@tappify/extension-sdk 2.0.0
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/LICENSE +21 -0
- package/dist/chunk-2DBSVKAU.cjs +65 -0
- package/dist/chunk-2DBSVKAU.cjs.map +1 -0
- package/dist/chunk-3YWVYA42.cjs +142 -0
- package/dist/chunk-3YWVYA42.cjs.map +1 -0
- package/dist/chunk-4C5Z3CIK.mjs +26 -0
- package/dist/chunk-4C5Z3CIK.mjs.map +1 -0
- package/dist/chunk-5AAPD5WV.cjs +250 -0
- package/dist/chunk-5AAPD5WV.cjs.map +1 -0
- package/dist/chunk-7KIKOH5H.cjs +26 -0
- package/dist/chunk-7KIKOH5H.cjs.map +1 -0
- package/dist/chunk-BDCX6LRQ.mjs +250 -0
- package/dist/chunk-BDCX6LRQ.mjs.map +1 -0
- package/dist/chunk-CSTYBKZC.mjs +65 -0
- package/dist/chunk-CSTYBKZC.mjs.map +1 -0
- package/dist/chunk-IGK2AP6J.mjs +142 -0
- package/dist/chunk-IGK2AP6J.mjs.map +1 -0
- package/dist/chunk-MNDVYPEP.mjs +302 -0
- package/dist/chunk-MNDVYPEP.mjs.map +1 -0
- package/dist/chunk-OPPYEA7F.cjs +33 -0
- package/dist/chunk-OPPYEA7F.cjs.map +1 -0
- package/dist/chunk-PYJMJF44.cjs +302 -0
- package/dist/chunk-PYJMJF44.cjs.map +1 -0
- package/dist/chunk-QHM7XNRK.mjs +33 -0
- package/dist/chunk-QHM7XNRK.mjs.map +1 -0
- package/dist/chunk-RMUWRESK.cjs +365 -0
- package/dist/chunk-RMUWRESK.cjs.map +1 -0
- package/dist/chunk-ZT35X7KL.mjs +365 -0
- package/dist/chunk-ZT35X7KL.mjs.map +1 -0
- package/dist/codegen.cjs +300 -0
- package/dist/codegen.cjs.map +1 -0
- package/dist/codegen.d.cts +432 -0
- package/dist/codegen.d.ts +432 -0
- package/dist/codegen.mjs +272 -0
- package/dist/codegen.mjs.map +1 -0
- package/dist/constants-DJ_KgZpN.d.cts +449 -0
- package/dist/constants-DJ_KgZpN.d.ts +449 -0
- package/dist/context-Dt6xh_5t.d.cts +30 -0
- package/dist/context-Ml7k_V14.d.ts +30 -0
- package/dist/errors-COsBh7kd.d.cts +88 -0
- package/dist/errors-COsBh7kd.d.ts +88 -0
- package/dist/handler-CZIlZ5vu.d.ts +227 -0
- package/dist/handler-kU9Jf36K.d.cts +227 -0
- package/dist/host.cjs +24 -0
- package/dist/host.cjs.map +1 -0
- package/dist/host.d.cts +24 -0
- package/dist/host.d.ts +24 -0
- package/dist/host.mjs +24 -0
- package/dist/host.mjs.map +1 -0
- package/dist/index.cjs +698 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1016 -0
- package/dist/index.d.ts +1016 -0
- package/dist/index.mjs +698 -0
- package/dist/index.mjs.map +1 -0
- package/dist/manifest.cjs +872 -0
- package/dist/manifest.cjs.map +1 -0
- package/dist/manifest.d.cts +432 -0
- package/dist/manifest.d.ts +432 -0
- package/dist/manifest.mjs +872 -0
- package/dist/manifest.mjs.map +1 -0
- package/dist/matchers-Cz93AK1n.d.cts +74 -0
- package/dist/matchers-Cz93AK1n.d.ts +74 -0
- package/dist/server.cjs +210 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +243 -0
- package/dist/server.d.ts +243 -0
- package/dist/server.mjs +210 -0
- package/dist/server.mjs.map +1 -0
- package/dist/styles.css +367 -0
- package/dist/testing/index.cjs +887 -0
- package/dist/testing/index.cjs.map +1 -0
- package/dist/testing/index.d.cts +512 -0
- package/dist/testing/index.d.ts +512 -0
- package/dist/testing/index.mjs +887 -0
- package/dist/testing/index.mjs.map +1 -0
- package/dist/testing/jest.cjs +17 -0
- package/dist/testing/jest.cjs.map +1 -0
- package/dist/testing/jest.d.cts +12 -0
- package/dist/testing/jest.d.ts +12 -0
- package/dist/testing/jest.mjs +17 -0
- package/dist/testing/jest.mjs.map +1 -0
- package/dist/testing/vitest.cjs +15 -0
- package/dist/testing/vitest.cjs.map +1 -0
- package/dist/testing/vitest.d.cts +10 -0
- package/dist/testing/vitest.d.ts +10 -0
- package/dist/testing/vitest.mjs +15 -0
- package/dist/testing/vitest.mjs.map +1 -0
- package/dist/types-DDAGLpov.d.ts +715 -0
- package/dist/types-DrpQSv_2.d.cts +715 -0
- package/dist/vite.cjs +1344 -0
- package/dist/vite.cjs.map +1 -0
- package/dist/vite.d.cts +434 -0
- package/dist/vite.d.ts +434 -0
- package/dist/vite.mjs +1314 -0
- package/dist/vite.mjs.map +1 -0
- package/package.json +268 -0
- package/schema/extension-v2.json +1500 -0
|
@@ -0,0 +1,887 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
var _chunk5AAPD5WVcjs = require('../chunk-5AAPD5WV.cjs');
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
var _chunk7KIKOH5Hcjs = require('../chunk-7KIKOH5H.cjs');
|
|
8
|
+
require('../chunk-OPPYEA7F.cjs');
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
var _chunkRMUWRESKcjs = require('../chunk-RMUWRESK.cjs');
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
var _chunkPYJMJF44cjs = require('../chunk-PYJMJF44.cjs');
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
var _chunk3YWVYA42cjs = require('../chunk-3YWVYA42.cjs');
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
var _chunk2DBSVKAUcjs = require('../chunk-2DBSVKAU.cjs');
|
|
29
|
+
|
|
30
|
+
// src/testing/client.ts
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
var _jose = require('jose');
|
|
36
|
+
|
|
37
|
+
// src/testing/envelope.ts
|
|
38
|
+
var DEFAULT_FILTERS = {
|
|
39
|
+
range: { from: "2026-09-01", to: "2026-09-08", preset: "7d" },
|
|
40
|
+
platform: "all",
|
|
41
|
+
country: "all"
|
|
42
|
+
};
|
|
43
|
+
function callEnvelope(envelope) {
|
|
44
|
+
const context = {
|
|
45
|
+
projectId: envelope.install.projectId,
|
|
46
|
+
platform: envelope.filters.platform,
|
|
47
|
+
from: envelope.filters.range.from,
|
|
48
|
+
to: envelope.filters.range.to,
|
|
49
|
+
filters: envelope.filters
|
|
50
|
+
};
|
|
51
|
+
return JSON.stringify({
|
|
52
|
+
install: envelope.install,
|
|
53
|
+
context,
|
|
54
|
+
input: envelope.input,
|
|
55
|
+
credentials: _nullishCoalesce(envelope.credentials, () => ( {})),
|
|
56
|
+
documents: _nullishCoalesce(envelope.documents, () => ( {})),
|
|
57
|
+
event: envelope.event
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function parseJson(text) {
|
|
61
|
+
try {
|
|
62
|
+
return { parsed: true, value: JSON.parse(text) };
|
|
63
|
+
} catch (e) {
|
|
64
|
+
return { parsed: false, value: void 0 };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function readEnvelopeResponse(response, call) {
|
|
68
|
+
const text = await response.text();
|
|
69
|
+
const body = parseJson(text);
|
|
70
|
+
const status = String(response.status);
|
|
71
|
+
if (response.ok) {
|
|
72
|
+
if (body.parsed) return body.value;
|
|
73
|
+
if (text.trim() === "") return void 0;
|
|
74
|
+
throw new (0, _chunk2DBSVKAUcjs.TapServerError)(
|
|
75
|
+
_chunkRMUWRESKcjs.SERVER_ERROR_CODES.INTERNAL_ERROR,
|
|
76
|
+
`The extension server answered ${status} for ${call} with a body that is not JSON. Every Tappify route answers JSON, or 204 with no body.`,
|
|
77
|
+
response.status
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
const error = _chunkRMUWRESKcjs.readRecord.call(void 0, body.value, "error");
|
|
81
|
+
throw new (0, _chunk2DBSVKAUcjs.TapServerError)(
|
|
82
|
+
_nullishCoalesce(_chunkRMUWRESKcjs.readText.call(void 0, error, "code"), () => ( _chunkRMUWRESKcjs.SERVER_ERROR_CODES.INTERNAL_ERROR)),
|
|
83
|
+
_nullishCoalesce(_chunkRMUWRESKcjs.readText.call(void 0, error, "message"), () => ( `The extension server answered ${status} for ${call} without a JSON error body.`)),
|
|
84
|
+
response.status
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/testing/client.ts
|
|
89
|
+
var TEST_KID = "tappify-test-key";
|
|
90
|
+
var TEST_ISSUER = "https://api.tappify.ai";
|
|
91
|
+
var keys = null;
|
|
92
|
+
function ensureKeys() {
|
|
93
|
+
_nullishCoalesce(keys, () => ( (keys = (async () => {
|
|
94
|
+
const pair = await _jose.generateKeyPair.call(void 0, "RS256", { extractable: true });
|
|
95
|
+
const publicJwk = await _jose.exportJWK.call(void 0, pair.publicKey);
|
|
96
|
+
return {
|
|
97
|
+
privateKey: pair.privateKey,
|
|
98
|
+
jwks: {
|
|
99
|
+
keys: [{ ...publicJwk, kid: TEST_KID, alg: "RS256", use: "sig" }]
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
})())));
|
|
103
|
+
return keys;
|
|
104
|
+
}
|
|
105
|
+
async function testJwks() {
|
|
106
|
+
return (await ensureKeys()).jwks;
|
|
107
|
+
}
|
|
108
|
+
async function signTestToken(claims = {}, options = {}) {
|
|
109
|
+
const { privateKey } = await ensureKeys();
|
|
110
|
+
const extensionId = _nullishCoalesce(_nullishCoalesce(options.extensionId, () => ( claims.extensionId)), () => ( "starter"));
|
|
111
|
+
return new (0, _jose.SignJWT)({
|
|
112
|
+
installId: _nullishCoalesce(claims.installId, () => ( "ins_test")),
|
|
113
|
+
extensionId,
|
|
114
|
+
projectId: _nullishCoalesce(claims.projectId, () => ( "prj_north")),
|
|
115
|
+
organizationId: _nullishCoalesce(claims.organizationId, () => ( "org_test")),
|
|
116
|
+
scopes: _nullishCoalesce(claims.scopes, () => ( [])),
|
|
117
|
+
...claims.userId === void 0 ? {} : { userId: claims.userId }
|
|
118
|
+
}).setProtectedHeader({ alg: "RS256", kid: TEST_KID }).setIssuer(TEST_ISSUER).setAudience(`ext:${extensionId}`).setIssuedAt().setExpirationTime(_nullishCoalesce(options.expiresIn, () => ( "5m"))).sign(privateKey);
|
|
119
|
+
}
|
|
120
|
+
var HEALTH_PATH = "/tappify/health";
|
|
121
|
+
function createTestClient(target, defaults = {}) {
|
|
122
|
+
if (typeof target === "function") {
|
|
123
|
+
throw new (0, _chunk2DBSVKAUcjs.TapError)(
|
|
124
|
+
"TAP_TEST_TARGET_INVALID",
|
|
125
|
+
"createTestClient() was handed a built handler instead of the options object. A handler from createTappifyHandler() verifies install tokens against the live Tappify key set, so every call the test client makes would come back 401. Pass the options themselves \u2014 createTestClient(handlerOptions) \u2014 or, when you need the handler, build it with createTappifyHandler({ ...handlerOptions, jwks: await testJwks() }) and drive it through createTapMock({ handler, extensionId: handlerOptions.extensionId }) so the test token carries the audience the handler expects."
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
const extensionId = target.extensionId;
|
|
129
|
+
const handlerPromise = testJwks().then(
|
|
130
|
+
(jwks) => _chunkRMUWRESKcjs.createTappifyHandler.call(void 0, { ...target, jwks })
|
|
131
|
+
);
|
|
132
|
+
const install = {
|
|
133
|
+
id: "ins_test",
|
|
134
|
+
extensionId,
|
|
135
|
+
projectId: "prj_north",
|
|
136
|
+
organizationId: "org_test",
|
|
137
|
+
...defaults.install
|
|
138
|
+
};
|
|
139
|
+
const filters = _nullishCoalesce(defaults.filters, () => ( DEFAULT_FILTERS));
|
|
140
|
+
const origin = _nullishCoalesce(defaults.origin, () => ( "https://vendor.test"));
|
|
141
|
+
let sent = 0;
|
|
142
|
+
function envelope(input, event) {
|
|
143
|
+
return callEnvelope({
|
|
144
|
+
install,
|
|
145
|
+
filters,
|
|
146
|
+
input,
|
|
147
|
+
credentials: defaults.credentials,
|
|
148
|
+
documents: defaults.documents,
|
|
149
|
+
event
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
async function send(method, path, body) {
|
|
153
|
+
const handler = await handlerPromise;
|
|
154
|
+
sent += 1;
|
|
155
|
+
const headers = new Headers({
|
|
156
|
+
"content-type": "application/json",
|
|
157
|
+
"x-tappify-event-id": _nullishCoalesce(defaults.eventId, () => ( `evt_${path}_${String(sent)}`))
|
|
158
|
+
});
|
|
159
|
+
if (path !== HEALTH_PATH) {
|
|
160
|
+
const token = await signTestToken(
|
|
161
|
+
{
|
|
162
|
+
installId: install.id,
|
|
163
|
+
extensionId: install.extensionId,
|
|
164
|
+
projectId: install.projectId,
|
|
165
|
+
organizationId: install.organizationId,
|
|
166
|
+
scopes: _nullishCoalesce(defaults.scopes, () => ( []))
|
|
167
|
+
},
|
|
168
|
+
{ extensionId }
|
|
169
|
+
);
|
|
170
|
+
headers.set("authorization", `Bearer ${token}`);
|
|
171
|
+
}
|
|
172
|
+
return handler(
|
|
173
|
+
new Request(`${origin}${path}`, {
|
|
174
|
+
method,
|
|
175
|
+
headers,
|
|
176
|
+
...body === void 0 ? {} : { body }
|
|
177
|
+
})
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
async function call(method, path, body) {
|
|
181
|
+
return readEnvelopeResponse(
|
|
182
|
+
await send(method, path, body),
|
|
183
|
+
`${method} ${path}`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
health: async () => await call("GET", HEALTH_PATH),
|
|
188
|
+
metrics: async (input) => await call(
|
|
189
|
+
"POST",
|
|
190
|
+
"/tappify/metrics",
|
|
191
|
+
envelope(input)
|
|
192
|
+
),
|
|
193
|
+
tool: (id, input) => call("POST", `/tappify/tools/${id}`, envelope(input)),
|
|
194
|
+
mention: async (id, query) => await call(
|
|
195
|
+
"GET",
|
|
196
|
+
`/tappify/mentions/${id}?q=${encodeURIComponent(query)}`
|
|
197
|
+
),
|
|
198
|
+
action: (id, input) => call("POST", `/tappify/actions/${id}`, envelope(input)),
|
|
199
|
+
procedure: (name, input = {}) => call("POST", `/tappify/procedures/${name}`, envelope(input)),
|
|
200
|
+
context: async (id) => await call("GET", `/tappify/context/${id}`),
|
|
201
|
+
event: async (name, payload) => {
|
|
202
|
+
await call("POST", "/tappify/events", envelope({}, { name, payload }));
|
|
203
|
+
},
|
|
204
|
+
work: (operation, input) => call("POST", `/tappify/work/${operation}`, envelope(input)),
|
|
205
|
+
raw: (method, path, body) => send(method, path, body === void 0 ? void 0 : JSON.stringify(body))
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/testing/fixtures.ts
|
|
210
|
+
var fixtureApps = [
|
|
211
|
+
{
|
|
212
|
+
id: "app_ios",
|
|
213
|
+
name: "Northlight",
|
|
214
|
+
platform: "ios",
|
|
215
|
+
storeId: "1490000001",
|
|
216
|
+
bundleId: "ai.tappify.northlight"
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
id: "app_android",
|
|
220
|
+
name: "Northlight",
|
|
221
|
+
platform: "android",
|
|
222
|
+
storeId: "ai.tappify.northlight",
|
|
223
|
+
bundleId: "ai.tappify.northlight"
|
|
224
|
+
}
|
|
225
|
+
];
|
|
226
|
+
var fixtureReleases = [
|
|
227
|
+
{
|
|
228
|
+
id: "rel_42",
|
|
229
|
+
version: "4.2.0",
|
|
230
|
+
build: "118",
|
|
231
|
+
platform: "ios",
|
|
232
|
+
state: "ready_for_sale",
|
|
233
|
+
shippedAt: "2026-09-01T09:00:00.000Z"
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
id: "rel_41",
|
|
237
|
+
version: "4.1.0",
|
|
238
|
+
build: "107",
|
|
239
|
+
platform: "ios",
|
|
240
|
+
state: "ready_for_sale",
|
|
241
|
+
shippedAt: "2026-08-11T09:00:00.000Z"
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
id: "rel_a42",
|
|
245
|
+
version: "4.2.0",
|
|
246
|
+
build: "4200",
|
|
247
|
+
platform: "android",
|
|
248
|
+
state: "published",
|
|
249
|
+
shippedAt: "2026-09-02T09:00:00.000Z"
|
|
250
|
+
}
|
|
251
|
+
];
|
|
252
|
+
var fixtureKeywords = [
|
|
253
|
+
{
|
|
254
|
+
id: "kw_1",
|
|
255
|
+
term: "photo editor",
|
|
256
|
+
platform: "ios",
|
|
257
|
+
country: "US",
|
|
258
|
+
position: 4,
|
|
259
|
+
popularity: 6.6
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
id: "kw_2",
|
|
263
|
+
term: "collage maker",
|
|
264
|
+
platform: "ios",
|
|
265
|
+
country: "US",
|
|
266
|
+
position: 11,
|
|
267
|
+
popularity: 5.3
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
id: "kw_3",
|
|
271
|
+
term: "photo editor",
|
|
272
|
+
platform: "android",
|
|
273
|
+
country: "US",
|
|
274
|
+
position: 7,
|
|
275
|
+
popularity: 6
|
|
276
|
+
}
|
|
277
|
+
];
|
|
278
|
+
var fixtureListings = [
|
|
279
|
+
{
|
|
280
|
+
platform: "ios",
|
|
281
|
+
country: "US",
|
|
282
|
+
locale: "en-US",
|
|
283
|
+
title: "Northlight \u2014 Photo Editor",
|
|
284
|
+
subtitle: "Edit, collage, share",
|
|
285
|
+
description: "Northlight turns a camera roll into a finished post.",
|
|
286
|
+
keywords: ["photo", "editor", "collage"],
|
|
287
|
+
updatedAt: "2026-08-28T12:00:00.000Z"
|
|
288
|
+
}
|
|
289
|
+
];
|
|
290
|
+
var fixtureProject = {
|
|
291
|
+
project: {
|
|
292
|
+
id: "prj_north",
|
|
293
|
+
name: "Northlight",
|
|
294
|
+
platforms: ["ios", "android"]
|
|
295
|
+
},
|
|
296
|
+
apps: fixtureApps,
|
|
297
|
+
releases: fixtureReleases
|
|
298
|
+
};
|
|
299
|
+
var SERIES_VALUES = {
|
|
300
|
+
downloads: [1180, 1240, 1310, 1290, 1402, 1512, 1488],
|
|
301
|
+
impressions: [21400, 22150, 22980, 22110, 24020, 25510, 25010],
|
|
302
|
+
page_views: [8120, 8340, 8710, 8290, 9110, 9640, 9420],
|
|
303
|
+
conversion_rate: [0.145, 0.149, 0.15, 0.156, 0.152, 0.157, 0.158]
|
|
304
|
+
};
|
|
305
|
+
function week() {
|
|
306
|
+
return [
|
|
307
|
+
"2026-09-01",
|
|
308
|
+
"2026-09-02",
|
|
309
|
+
"2026-09-03",
|
|
310
|
+
"2026-09-04",
|
|
311
|
+
"2026-09-05",
|
|
312
|
+
"2026-09-06",
|
|
313
|
+
"2026-09-07"
|
|
314
|
+
];
|
|
315
|
+
}
|
|
316
|
+
function fixtureSeries(metric) {
|
|
317
|
+
const points = week().map((at, index) => ({
|
|
318
|
+
at,
|
|
319
|
+
value: SERIES_VALUES[metric][index]
|
|
320
|
+
}));
|
|
321
|
+
return {
|
|
322
|
+
metric,
|
|
323
|
+
unit: metric === "conversion_rate" ? "ratio" : "count",
|
|
324
|
+
points
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
var fixtureReviews = {
|
|
328
|
+
total: 3,
|
|
329
|
+
averageRating: 3.7,
|
|
330
|
+
reviews: [
|
|
331
|
+
{
|
|
332
|
+
id: "rev_1",
|
|
333
|
+
platform: "ios",
|
|
334
|
+
country: "US",
|
|
335
|
+
rating: 5,
|
|
336
|
+
title: "Fast and clean",
|
|
337
|
+
body: "The new collage layouts are exactly what I wanted.",
|
|
338
|
+
author: "kbrady",
|
|
339
|
+
submittedAt: "2026-09-05T18:22:00.000Z",
|
|
340
|
+
replied: false
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
id: "rev_2",
|
|
344
|
+
platform: "ios",
|
|
345
|
+
country: "GB",
|
|
346
|
+
rating: 2,
|
|
347
|
+
title: "Crashes on export",
|
|
348
|
+
body: "Since 4.2 the export button closes the app.",
|
|
349
|
+
author: null,
|
|
350
|
+
submittedAt: "2026-09-04T08:10:00.000Z",
|
|
351
|
+
replied: true
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
id: "rev_3",
|
|
355
|
+
platform: "android",
|
|
356
|
+
country: "DE",
|
|
357
|
+
rating: 4,
|
|
358
|
+
title: null,
|
|
359
|
+
body: "Gute App, aber der Export dauert lange.",
|
|
360
|
+
author: "mgruber",
|
|
361
|
+
submittedAt: "2026-09-03T16:45:00.000Z",
|
|
362
|
+
replied: false
|
|
363
|
+
}
|
|
364
|
+
]
|
|
365
|
+
};
|
|
366
|
+
var fixtureCrashes = {
|
|
367
|
+
crashFreeRate: 99.42,
|
|
368
|
+
issues: [
|
|
369
|
+
{
|
|
370
|
+
id: "crash_1",
|
|
371
|
+
title: "ExportSession.finish \u2014 index out of range",
|
|
372
|
+
count: 214,
|
|
373
|
+
build: "118",
|
|
374
|
+
firstSeenAt: "2026-09-01T11:02:00.000Z",
|
|
375
|
+
lastSeenAt: "2026-09-07T21:40:00.000Z"
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
id: "crash_2",
|
|
379
|
+
title: "PhotoLibrary.load \u2014 permission revoked",
|
|
380
|
+
count: 37,
|
|
381
|
+
build: "118",
|
|
382
|
+
firstSeenAt: "2026-09-02T07:15:00.000Z",
|
|
383
|
+
lastSeenAt: "2026-09-06T13:05:00.000Z"
|
|
384
|
+
}
|
|
385
|
+
]
|
|
386
|
+
};
|
|
387
|
+
var fixtureRevenue = {
|
|
388
|
+
currency: "USD",
|
|
389
|
+
mrr: 41200,
|
|
390
|
+
refunds: 18,
|
|
391
|
+
points: week().map((at, index) => ({
|
|
392
|
+
at,
|
|
393
|
+
value: [1310, 1355, 1401, 1388, 1470, 1525, 1502][index]
|
|
394
|
+
}))
|
|
395
|
+
};
|
|
396
|
+
var fixtures = {
|
|
397
|
+
project: fixtureProject,
|
|
398
|
+
apps: fixtureApps,
|
|
399
|
+
releases: fixtureReleases,
|
|
400
|
+
keywords: fixtureKeywords,
|
|
401
|
+
listings: fixtureListings,
|
|
402
|
+
reviews: fixtureReviews,
|
|
403
|
+
crashes: fixtureCrashes,
|
|
404
|
+
revenue: fixtureRevenue,
|
|
405
|
+
series: fixtureSeries
|
|
406
|
+
};
|
|
407
|
+
function answerFixtureQuery(query) {
|
|
408
|
+
switch (query.kind) {
|
|
409
|
+
case "project":
|
|
410
|
+
return structuredClone(fixtureProject);
|
|
411
|
+
case "series":
|
|
412
|
+
return structuredClone(fixtureSeries(query.metric));
|
|
413
|
+
case "keywords":
|
|
414
|
+
return structuredClone({ keywords: fixtureKeywords });
|
|
415
|
+
case "listing":
|
|
416
|
+
return structuredClone({ listings: fixtureListings });
|
|
417
|
+
case "reviews":
|
|
418
|
+
return structuredClone(fixtureReviews);
|
|
419
|
+
case "crashes":
|
|
420
|
+
return structuredClone(fixtureCrashes);
|
|
421
|
+
case "revenue":
|
|
422
|
+
return structuredClone(fixtureRevenue);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// src/testing/mock.ts
|
|
427
|
+
var SINGLETON_ID = "singleton";
|
|
428
|
+
var PROBE_KEYS = /* @__PURE__ */ new Set([
|
|
429
|
+
"then",
|
|
430
|
+
"toJSON",
|
|
431
|
+
"$$typeof",
|
|
432
|
+
"constructor",
|
|
433
|
+
"prototype",
|
|
434
|
+
"valueOf",
|
|
435
|
+
"toString",
|
|
436
|
+
"inspect",
|
|
437
|
+
"asymmetricMatch",
|
|
438
|
+
"nodeType"
|
|
439
|
+
]);
|
|
440
|
+
function unknownCollectionError(collection) {
|
|
441
|
+
return new (0, _chunk2DBSVKAUcjs.TapError)(
|
|
442
|
+
"TAP_UNKNOWN_COLLECTION",
|
|
443
|
+
`This mock exposes no storage collection called "${collection}". createTapMock({ storage: \u2026 }) was given an explicit map, so only the collections it names exist. Add "${collection}" to that map, or drop the storage option to expose every collection.`
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
function singletonView(handle) {
|
|
447
|
+
return {
|
|
448
|
+
get: () => handle.get(),
|
|
449
|
+
set: (document2) => handle.set(document2),
|
|
450
|
+
patch: (partial) => handle.patch(partial)
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
function collectionView(handle) {
|
|
454
|
+
return {
|
|
455
|
+
list: (listOptions) => handle.list(listOptions),
|
|
456
|
+
get: (id) => handle.get(id),
|
|
457
|
+
put: (id, document2) => handle.put(id, document2),
|
|
458
|
+
delete: (id) => handle.delete(id)
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
function scopeError(scope) {
|
|
462
|
+
return new (0, _chunk2DBSVKAUcjs.TapError)(
|
|
463
|
+
"TAP_SCOPE_MISSING",
|
|
464
|
+
`This install has not granted ${scope}. Declare it under "scopes" in tappify.extension.json, or pass scopes: ["${scope}"] to renderWithTap() to test the granted path.`
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
function asObject(value) {
|
|
468
|
+
return typeof value === "object" && value !== null ? value : {};
|
|
469
|
+
}
|
|
470
|
+
function createTapMock(options = {}) {
|
|
471
|
+
const granted = new Set(_nullishCoalesce(options.scopes, () => ( _chunkPYJMJF44cjs.SCOPE_KEYS)));
|
|
472
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
473
|
+
const eventHandlers = /* @__PURE__ */ new Map();
|
|
474
|
+
const state = /* @__PURE__ */ new Map();
|
|
475
|
+
const store = /* @__PURE__ */ new Map();
|
|
476
|
+
const portal = document.createElement("div");
|
|
477
|
+
portal.dataset.tapPortal = "true";
|
|
478
|
+
const calls = {
|
|
479
|
+
navigations: [],
|
|
480
|
+
searches: [],
|
|
481
|
+
toasts: [],
|
|
482
|
+
confirms: [],
|
|
483
|
+
actions: [],
|
|
484
|
+
telemetry: [],
|
|
485
|
+
chats: [],
|
|
486
|
+
externals: [],
|
|
487
|
+
downloads: [],
|
|
488
|
+
copies: [],
|
|
489
|
+
cards: [],
|
|
490
|
+
expands: 0,
|
|
491
|
+
settingsOpens: 0
|
|
492
|
+
};
|
|
493
|
+
const emitChange = (key) => {
|
|
494
|
+
for (const listener of _nullishCoalesce(listeners.get(key), () => ( []))) listener();
|
|
495
|
+
};
|
|
496
|
+
const subscribe = (key, listener) => {
|
|
497
|
+
const set = _nullishCoalesce(listeners.get(key), () => ( /* @__PURE__ */ new Set()));
|
|
498
|
+
set.add(listener);
|
|
499
|
+
listeners.set(key, set);
|
|
500
|
+
return () => {
|
|
501
|
+
set.delete(listener);
|
|
502
|
+
};
|
|
503
|
+
};
|
|
504
|
+
const withStorage = (run) => granted.has("storage:write") ? Promise.resolve(run()) : Promise.reject(scopeError("storage:write"));
|
|
505
|
+
const bucket = (collection) => {
|
|
506
|
+
const existing = store.get(collection);
|
|
507
|
+
if (existing) return existing;
|
|
508
|
+
const created = [];
|
|
509
|
+
store.set(collection, created);
|
|
510
|
+
return created;
|
|
511
|
+
};
|
|
512
|
+
function buildHandle(collection) {
|
|
513
|
+
const find = (id) => bucket(collection).find((entry) => entry.id === id);
|
|
514
|
+
const write = (id, data) => {
|
|
515
|
+
const items = bucket(collection);
|
|
516
|
+
const index = items.findIndex((entry) => entry.id === id);
|
|
517
|
+
if (index === -1) items.push({ id, data });
|
|
518
|
+
else items[index] = { id, data };
|
|
519
|
+
emitChange(`storage:${collection}`);
|
|
520
|
+
};
|
|
521
|
+
return {
|
|
522
|
+
get: (id) => withStorage(() => _nullishCoalesce(_optionalChain([find, 'call', _ => _(_nullishCoalesce(id, () => ( SINGLETON_ID))), 'optionalAccess', _2 => _2.data]), () => ( null))),
|
|
523
|
+
set: (document2) => withStorage(() => {
|
|
524
|
+
write(SINGLETON_ID, document2);
|
|
525
|
+
}),
|
|
526
|
+
patch: (partial) => withStorage(() => {
|
|
527
|
+
const merged = { ...asObject(_optionalChain([find, 'call', _3 => _3(SINGLETON_ID), 'optionalAccess', _4 => _4.data])), ...partial };
|
|
528
|
+
write(SINGLETON_ID, merged);
|
|
529
|
+
return merged;
|
|
530
|
+
}),
|
|
531
|
+
list: () => withStorage(() => ({
|
|
532
|
+
items: bucket(collection).filter((entry) => entry.id !== SINGLETON_ID).map((entry) => ({ ...asObject(entry.data), id: entry.id }))
|
|
533
|
+
})),
|
|
534
|
+
put: (id, document2) => withStorage(() => {
|
|
535
|
+
write(id, document2);
|
|
536
|
+
}),
|
|
537
|
+
delete: (id) => withStorage(() => {
|
|
538
|
+
const items = bucket(collection);
|
|
539
|
+
const index = items.findIndex((entry) => entry.id === id);
|
|
540
|
+
if (index !== -1) {
|
|
541
|
+
items.splice(index, 1);
|
|
542
|
+
emitChange(`storage:${collection}`);
|
|
543
|
+
}
|
|
544
|
+
})
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
const handles = /* @__PURE__ */ new Map();
|
|
548
|
+
const kinds = options.storage;
|
|
549
|
+
const exposeHandle = (collection) => {
|
|
550
|
+
const kind = _optionalChain([kinds, 'optionalAccess', _5 => _5[collection]]);
|
|
551
|
+
if (kinds !== void 0 && kind === void 0) {
|
|
552
|
+
throw unknownCollectionError(collection);
|
|
553
|
+
}
|
|
554
|
+
const handle = buildHandle(collection);
|
|
555
|
+
if (kind === "singleton") return singletonView(handle);
|
|
556
|
+
if (kind === "collection") return collectionView(handle);
|
|
557
|
+
return handle;
|
|
558
|
+
};
|
|
559
|
+
const handleFor = (collection) => {
|
|
560
|
+
if (PROBE_KEYS.has(collection)) return void 0;
|
|
561
|
+
const existing = handles.get(collection);
|
|
562
|
+
if (existing) return existing;
|
|
563
|
+
const created = exposeHandle(collection);
|
|
564
|
+
handles.set(collection, created);
|
|
565
|
+
return created;
|
|
566
|
+
};
|
|
567
|
+
const storageTarget = {};
|
|
568
|
+
const storage = new Proxy(storageTarget, {
|
|
569
|
+
get: (_target, property) => typeof property === "string" ? handleFor(property) : void 0,
|
|
570
|
+
has: () => true,
|
|
571
|
+
ownKeys: () => [...store.keys()],
|
|
572
|
+
getOwnPropertyDescriptor: () => ({ enumerable: true, configurable: true })
|
|
573
|
+
});
|
|
574
|
+
let calledProcedures = 0;
|
|
575
|
+
let signedToken = null;
|
|
576
|
+
function handlerToken() {
|
|
577
|
+
if (options.handlerToken !== void 0) {
|
|
578
|
+
return Promise.resolve(options.handlerToken);
|
|
579
|
+
}
|
|
580
|
+
_nullishCoalesce(signedToken, () => ( (signedToken = signTestToken(
|
|
581
|
+
{
|
|
582
|
+
installId: tap.extension.installId,
|
|
583
|
+
extensionId: tap.extension.id,
|
|
584
|
+
projectId: tap.extension.projectId,
|
|
585
|
+
organizationId: tap.auth.organizationId,
|
|
586
|
+
scopes: [...granted]
|
|
587
|
+
},
|
|
588
|
+
{ extensionId: tap.extension.id }
|
|
589
|
+
))));
|
|
590
|
+
return signedToken;
|
|
591
|
+
}
|
|
592
|
+
async function callProcedure(name, input) {
|
|
593
|
+
const local = _optionalChain([options, 'access', _6 => _6.server, 'optionalAccess', _7 => _7[name]]);
|
|
594
|
+
if (local) return local(input);
|
|
595
|
+
if (options.handler) {
|
|
596
|
+
const path = `/tappify/procedures/${name}`;
|
|
597
|
+
calledProcedures += 1;
|
|
598
|
+
const response = await options.handler(
|
|
599
|
+
new Request(`https://vendor.test${path}`, {
|
|
600
|
+
method: "POST",
|
|
601
|
+
headers: {
|
|
602
|
+
"content-type": "application/json",
|
|
603
|
+
"x-tappify-event-id": `evt_${name}_${String(calledProcedures)}`,
|
|
604
|
+
authorization: `Bearer ${await handlerToken()}`
|
|
605
|
+
},
|
|
606
|
+
body: callEnvelope({
|
|
607
|
+
install: {
|
|
608
|
+
id: tap.extension.installId,
|
|
609
|
+
extensionId: tap.extension.id,
|
|
610
|
+
projectId: tap.extension.projectId,
|
|
611
|
+
organizationId: tap.auth.organizationId
|
|
612
|
+
},
|
|
613
|
+
filters: tap.filters,
|
|
614
|
+
input
|
|
615
|
+
})
|
|
616
|
+
})
|
|
617
|
+
);
|
|
618
|
+
return readEnvelopeResponse(response, `POST ${path}`);
|
|
619
|
+
}
|
|
620
|
+
throw new (0, _chunk2DBSVKAUcjs.TapError)(
|
|
621
|
+
"TAP_UNKNOWN_PROCEDURE",
|
|
622
|
+
`No handler is registered for the procedure "${name}". Pass server: { ${name}: input => \u2026 } or handler: <your createTappifyHandler handler> to createTapMock().`
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
const serverTarget = {};
|
|
626
|
+
const tap = {
|
|
627
|
+
extension: {
|
|
628
|
+
id: _nullishCoalesce(options.extensionId, () => ( "starter")),
|
|
629
|
+
name: _nullishCoalesce(options.extensionName, () => ( "Starter")),
|
|
630
|
+
installId: _nullishCoalesce(options.installId, () => ( "ins_test")),
|
|
631
|
+
projectId: "prj_north"
|
|
632
|
+
},
|
|
633
|
+
env: "dev",
|
|
634
|
+
host: { version: "test", sdkMajors: [2] },
|
|
635
|
+
auth: {
|
|
636
|
+
token: "test-token",
|
|
637
|
+
user: { id: "usr_test", role: "admin" },
|
|
638
|
+
organizationId: "org_test",
|
|
639
|
+
scopes: [...granted],
|
|
640
|
+
can: (scope) => granted.has(scope)
|
|
641
|
+
},
|
|
642
|
+
project: {
|
|
643
|
+
id: "prj_north",
|
|
644
|
+
name: "Northlight",
|
|
645
|
+
platforms: ["ios", "android"],
|
|
646
|
+
apps: [],
|
|
647
|
+
releases: [],
|
|
648
|
+
keywords: [],
|
|
649
|
+
...options.project
|
|
650
|
+
},
|
|
651
|
+
filters: { ...DEFAULT_FILTERS, ...options.filters },
|
|
652
|
+
theme: { mode: _nullishCoalesce(options.theme, () => ( "light")) },
|
|
653
|
+
locale: "en-US",
|
|
654
|
+
timezone: "UTC",
|
|
655
|
+
format: {
|
|
656
|
+
number: (value, formatOptions) => new Intl.NumberFormat("en-US", formatOptions).format(value),
|
|
657
|
+
currency: (value, code) => new Intl.NumberFormat("en-US", {
|
|
658
|
+
style: "currency",
|
|
659
|
+
currency: code
|
|
660
|
+
}).format(value),
|
|
661
|
+
date: (value) => new Date(value).toISOString().slice(0, 10),
|
|
662
|
+
relative: (value) => new Date(value).toISOString()
|
|
663
|
+
},
|
|
664
|
+
nav: {
|
|
665
|
+
push: (path) => calls.navigations.push(path),
|
|
666
|
+
setSearch: (params) => calls.searches.push(params),
|
|
667
|
+
openSettings: () => {
|
|
668
|
+
calls.settingsOpens += 1;
|
|
669
|
+
},
|
|
670
|
+
openChat: (prompt, context) => calls.chats.push({ prompt, context }),
|
|
671
|
+
openExternal: (url) => calls.externals.push(url)
|
|
672
|
+
},
|
|
673
|
+
data: {
|
|
674
|
+
query: (query) => {
|
|
675
|
+
const scope = _chunkPYJMJF44cjs.QUERY_SCOPES[query.kind];
|
|
676
|
+
if (!granted.has(scope)) return Promise.reject(scopeError(scope));
|
|
677
|
+
return Promise.resolve(answerFixtureQuery(query));
|
|
678
|
+
},
|
|
679
|
+
subscribe: (event, handler) => {
|
|
680
|
+
const key = String(event);
|
|
681
|
+
const set = _nullishCoalesce(eventHandlers.get(key), () => ( /* @__PURE__ */ new Set()));
|
|
682
|
+
const wrapped = (payload) => {
|
|
683
|
+
handler(payload);
|
|
684
|
+
};
|
|
685
|
+
set.add(wrapped);
|
|
686
|
+
eventHandlers.set(key, set);
|
|
687
|
+
return () => {
|
|
688
|
+
set.delete(wrapped);
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
},
|
|
692
|
+
server: new Proxy(serverTarget, {
|
|
693
|
+
get: (_target, property) => (input) => callProcedure(String(property), input),
|
|
694
|
+
has: () => true
|
|
695
|
+
}),
|
|
696
|
+
state: {
|
|
697
|
+
get: (key) => {
|
|
698
|
+
const value = state.get(key);
|
|
699
|
+
return value === void 0 ? void 0 : value;
|
|
700
|
+
},
|
|
701
|
+
set: (key, value) => {
|
|
702
|
+
state.set(key, value);
|
|
703
|
+
emitChange(`state:${key}`);
|
|
704
|
+
},
|
|
705
|
+
subscribe: (key, handler) => subscribe(`state:${key}`, handler)
|
|
706
|
+
},
|
|
707
|
+
ui: {
|
|
708
|
+
size: _nullishCoalesce(options.size, () => ( "slot")),
|
|
709
|
+
toast: (message, tone = "neutral") => calls.toasts.push({ message, tone }),
|
|
710
|
+
confirm: (confirmOptions) => {
|
|
711
|
+
calls.confirms.push(confirmOptions);
|
|
712
|
+
return Promise.resolve(_nullishCoalesce(options.confirmAnswer, () => ( true)));
|
|
713
|
+
},
|
|
714
|
+
openInChat: (card) => calls.cards.push(card),
|
|
715
|
+
expand: () => {
|
|
716
|
+
calls.expands += 1;
|
|
717
|
+
},
|
|
718
|
+
download: (blob, filename) => calls.downloads.push({ filename, size: blob.size }),
|
|
719
|
+
copy: (text) => {
|
|
720
|
+
calls.copies.push(text);
|
|
721
|
+
return Promise.resolve();
|
|
722
|
+
}
|
|
723
|
+
},
|
|
724
|
+
context: _nullishCoalesce(options.context, () => ( {})),
|
|
725
|
+
params: _nullishCoalesce(options.params, () => ( {})),
|
|
726
|
+
storage,
|
|
727
|
+
telemetry: {
|
|
728
|
+
event: (name, props) => calls.telemetry.push({ name, props })
|
|
729
|
+
},
|
|
730
|
+
actions: {
|
|
731
|
+
run: (actionId, input) => {
|
|
732
|
+
calls.actions.push({ actionId, input });
|
|
733
|
+
return Promise.resolve({
|
|
734
|
+
id: `run_${String(calls.actions.length)}`,
|
|
735
|
+
actionId,
|
|
736
|
+
status: "pending",
|
|
737
|
+
requestedAt: (/* @__PURE__ */ new Date(0)).toISOString()
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
},
|
|
741
|
+
invalidate: () => void 0,
|
|
742
|
+
__portal: portal,
|
|
743
|
+
__subscribe: subscribe
|
|
744
|
+
};
|
|
745
|
+
return {
|
|
746
|
+
tap,
|
|
747
|
+
calls,
|
|
748
|
+
portal,
|
|
749
|
+
documents: (collection, documentId) => bucket(collection).filter((entry) => documentId === void 0 || entry.id === documentId).map((entry) => entry.data),
|
|
750
|
+
emit: (event, payload) => {
|
|
751
|
+
for (const handler of _nullishCoalesce(eventHandlers.get(String(event)), () => ( []))) {
|
|
752
|
+
handler(payload);
|
|
753
|
+
}
|
|
754
|
+
},
|
|
755
|
+
setFilters: (filters) => {
|
|
756
|
+
tap.filters = filters;
|
|
757
|
+
emitChange("filters");
|
|
758
|
+
},
|
|
759
|
+
setSize: (size) => {
|
|
760
|
+
tap.ui = { ...tap.ui, size };
|
|
761
|
+
emitChange("size");
|
|
762
|
+
},
|
|
763
|
+
setTheme: (mode) => {
|
|
764
|
+
tap.theme = { mode };
|
|
765
|
+
emitChange("theme");
|
|
766
|
+
},
|
|
767
|
+
setParams: (params) => {
|
|
768
|
+
tap.params = params;
|
|
769
|
+
emitChange("params");
|
|
770
|
+
}
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// src/testing/render.tsx
|
|
775
|
+
var _reactquery = require('@tanstack/react-query');
|
|
776
|
+
|
|
777
|
+
|
|
778
|
+
var _react = require('@testing-library/react');
|
|
779
|
+
var _jsxruntime = require('react/jsx-runtime');
|
|
780
|
+
function renderWithTap(ui, options = {}) {
|
|
781
|
+
const mock = _nullishCoalesce(options.mock, () => ( createTapMock(options)));
|
|
782
|
+
const queryClient = _nullishCoalesce(options.queryClient, () => ( new (0, _reactquery.QueryClient)({
|
|
783
|
+
defaultOptions: { queries: { retry: false, gcTime: 0 } }
|
|
784
|
+
})));
|
|
785
|
+
const installId = mock.tap.extension.installId;
|
|
786
|
+
mock.tap.invalidate = (name) => {
|
|
787
|
+
void queryClient.invalidateQueries({
|
|
788
|
+
queryKey: name === void 0 ? _chunk5AAPD5WVcjs.installPrefix.call(void 0, installId) : _chunk5AAPD5WVcjs.procedurePrefix.call(void 0, installId, name)
|
|
789
|
+
});
|
|
790
|
+
};
|
|
791
|
+
function Wrapper({ children }) {
|
|
792
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _reactquery.QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk7KIKOH5Hcjs.TapHostProvider, { tap: mock.tap, children }) });
|
|
793
|
+
}
|
|
794
|
+
const result = _react.render.call(void 0, ui, { wrapper: Wrapper, container: options.container });
|
|
795
|
+
result.baseElement.append(mock.portal);
|
|
796
|
+
return { ...result, tap: mock.tap, mock, queryClient };
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// src/testing/validators.ts
|
|
800
|
+
function record(value) {
|
|
801
|
+
return typeof value === "object" && value !== null ? value : null;
|
|
802
|
+
}
|
|
803
|
+
function list(value) {
|
|
804
|
+
return Array.isArray(value) ? value : null;
|
|
805
|
+
}
|
|
806
|
+
function isPoint(value) {
|
|
807
|
+
const pair = list(value);
|
|
808
|
+
return pair !== null && pair.length === 2 && typeof pair[0] === "string" && typeof pair[1] === "number";
|
|
809
|
+
}
|
|
810
|
+
function validateMetricsResponse(response, metrics) {
|
|
811
|
+
const series = list(_optionalChain([record, 'call', _8 => _8(response), 'optionalAccess', _9 => _9.series]));
|
|
812
|
+
if (series === null) {
|
|
813
|
+
return [
|
|
814
|
+
'A metrics response is { "series": [...] }. Return one entry per declared metric, even when it has no points.'
|
|
815
|
+
];
|
|
816
|
+
}
|
|
817
|
+
const issues = [];
|
|
818
|
+
const declared = new Map(metrics.map((metric) => [metric.key, metric]));
|
|
819
|
+
for (const [index, entry] of series.entries()) {
|
|
820
|
+
const item = record(entry);
|
|
821
|
+
const key = typeof _optionalChain([item, 'optionalAccess', _10 => _10.metric]) === "string" ? item.metric : null;
|
|
822
|
+
if (item === null || key === null) {
|
|
823
|
+
issues.push(`series[${String(index)}] has no "metric" key.`);
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
const metric = declared.get(key);
|
|
827
|
+
if (metric === void 0) {
|
|
828
|
+
issues.push(
|
|
829
|
+
`series[${String(index)}] reports "${key}", which is not declared under contributes.connector.metrics.`
|
|
830
|
+
);
|
|
831
|
+
continue;
|
|
832
|
+
}
|
|
833
|
+
if (item.unit !== metric.unit) {
|
|
834
|
+
issues.push(
|
|
835
|
+
`series[${String(index)}] reports unit "${String(item.unit)}" but "${key}" is declared as "${metric.unit}".`
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
const points = list(item.points);
|
|
839
|
+
if (points === null) {
|
|
840
|
+
issues.push(`series[${String(index)}] has no "points" array.`);
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
for (const [pointIndex, point] of points.entries()) {
|
|
844
|
+
if (!isPoint(point)) {
|
|
845
|
+
issues.push(
|
|
846
|
+
`series[${String(index)}].points[${String(pointIndex)}] must be a two-element [timestamp, value] pair: a string and a number.`
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
return issues;
|
|
852
|
+
}
|
|
853
|
+
function validateToolResponse(value, returns) {
|
|
854
|
+
const body = record(value);
|
|
855
|
+
if (body === null) {
|
|
856
|
+
return [
|
|
857
|
+
`A "${returns}" tool returns an object the host draws as a card, not ${typeof value}.`
|
|
858
|
+
];
|
|
859
|
+
}
|
|
860
|
+
return _chunkPYJMJF44cjs.CARD_KEYS[returns].filter((key) => !(key in body)).map(
|
|
861
|
+
(key) => `A "${returns}" tool response needs a "${key}" key; the host draws the card from it.`
|
|
862
|
+
);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
|
|
866
|
+
|
|
867
|
+
|
|
868
|
+
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
|
|
872
|
+
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
exports.answerFixtureQuery = answerFixtureQuery; exports.createTapMock = createTapMock; exports.createTestClient = createTestClient; exports.fixtureApps = fixtureApps; exports.fixtureCrashes = fixtureCrashes; exports.fixtureKeywords = fixtureKeywords; exports.fixtureListings = fixtureListings; exports.fixtureProject = fixtureProject; exports.fixtureReleases = fixtureReleases; exports.fixtureRevenue = fixtureRevenue; exports.fixtureReviews = fixtureReviews; exports.fixtureSeries = fixtureSeries; exports.fixtures = fixtures; exports.installHostTheme = _chunk3YWVYA42cjs.installHostTheme; exports.registerTapMatchers = _chunk3YWVYA42cjs.registerTapMatchers; exports.renderWithTap = renderWithTap; exports.signTestToken = signTestToken; exports.tapMatchers = _chunk3YWVYA42cjs.tapMatchers; exports.testJwks = testJwks; exports.validateMetricsResponse = validateMetricsResponse; exports.validateToolResponse = validateToolResponse;
|
|
887
|
+
//# sourceMappingURL=index.cjs.map
|