@igstack/app-catalog-test-kit 0.18.6
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/README.md +51 -0
- package/dist/esm/harness/MockBackendVerifier.d.ts +8 -0
- package/dist/esm/harness/MockBackendVerifier.js +15 -0
- package/dist/esm/harness/MockBackendVerifier.js.map +1 -0
- package/dist/esm/harness/given.d.ts +23 -0
- package/dist/esm/harness/given.js +144 -0
- package/dist/esm/harness/given.js.map +1 -0
- package/dist/esm/index.d.ts +38 -0
- package/dist/esm/index.js +45 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/mock-backend/BrowserStateCfg.d.ts +18 -0
- package/dist/esm/mock-backend/BrowserStateCfg.js +33 -0
- package/dist/esm/mock-backend/BrowserStateCfg.js.map +1 -0
- package/dist/esm/mock-backend/MockBackendConfigurer.d.ts +17 -0
- package/dist/esm/mock-backend/MockBackendConfigurer.js +80 -0
- package/dist/esm/mock-backend/MockBackendConfigurer.js.map +1 -0
- package/dist/esm/mock-backend/MockDb.d.ts +18 -0
- package/dist/esm/mock-backend/MockDb.js +56 -0
- package/dist/esm/mock-backend/MockDb.js.map +1 -0
- package/dist/esm/mock-backend/MockService.d.ts +10 -0
- package/dist/esm/mock-backend/MockService.js +16 -0
- package/dist/esm/mock-backend/MockService.js.map +1 -0
- package/dist/esm/mock-backend/MockUserContext.d.ts +11 -0
- package/dist/esm/mock-backend/MockUserContext.js +38 -0
- package/dist/esm/mock-backend/MockUserContext.js.map +1 -0
- package/dist/esm/mock-backend/magazines.d.ts +22 -0
- package/dist/esm/mock-backend/magazines.js +186 -0
- package/dist/esm/mock-backend/magazines.js.map +1 -0
- package/dist/esm/mock-network/NetworkCatalog.d.ts +12 -0
- package/dist/esm/mock-network/NetworkCatalog.js +24 -0
- package/dist/esm/mock-network/NetworkCatalog.js.map +1 -0
- package/dist/esm/mock-network/NetworkConfigurerCfg.d.ts +13 -0
- package/dist/esm/mock-network/NetworkConfigurerCfg.js +16 -0
- package/dist/esm/mock-network/NetworkConfigurerCfg.js.map +1 -0
- package/dist/esm/mock-network/SharedNetwork.d.ts +13 -0
- package/dist/esm/mock-network/SharedNetwork.js +149 -0
- package/dist/esm/mock-network/SharedNetwork.js.map +1 -0
- package/dist/esm/mock-network/errorFactories.d.ts +11 -0
- package/dist/esm/mock-network/errorFactories.js +22 -0
- package/dist/esm/mock-network/errorFactories.js.map +1 -0
- package/dist/esm/mock-network/makeNetworkReplyWithCatalog.d.ts +3 -0
- package/dist/esm/mock-network/makeNetworkReplyWithCatalog.js +17 -0
- package/dist/esm/mock-network/makeNetworkReplyWithCatalog.js.map +1 -0
- package/dist/esm/setup/polyfillLocalStorage.d.ts +0 -0
- package/dist/esm/setup/polyfillLocalStorage.js +25 -0
- package/dist/esm/setup/polyfillLocalStorage.js.map +1 -0
- package/dist/esm/setup/svgMock.d.ts +1 -0
- package/dist/esm/setup/testSetup.d.ts +0 -0
- package/dist/esm/setup/testSetup.js +58 -0
- package/dist/esm/setup/testSetup.js.map +1 -0
- package/dist/esm/tools/AppDetailTools.d.ts +63 -0
- package/dist/esm/tools/AppDetailTools.js +180 -0
- package/dist/esm/tools/AppDetailTools.js.map +1 -0
- package/dist/esm/tools/BrowserState.d.ts +25 -0
- package/dist/esm/tools/BrowserState.js +30 -0
- package/dist/esm/tools/BrowserState.js.map +1 -0
- package/dist/esm/tools/CatalogTools.d.ts +67 -0
- package/dist/esm/tools/CatalogTools.js +202 -0
- package/dist/esm/tools/CatalogTools.js.map +1 -0
- package/dist/esm/tools/ErrorTools.d.ts +15 -0
- package/dist/esm/tools/ErrorTools.js +34 -0
- package/dist/esm/tools/ErrorTools.js.map +1 -0
- package/dist/esm/tools/GalleryTools.d.ts +6 -0
- package/dist/esm/tools/GalleryTools.js +22 -0
- package/dist/esm/tools/GalleryTools.js.map +1 -0
- package/dist/esm/tools/suppressConsole.d.ts +19 -0
- package/dist/esm/tools/suppressConsole.js +44 -0
- package/dist/esm/tools/suppressConsole.js.map +1 -0
- package/package.json +87 -0
- package/src/harness/MockBackendVerifier.ts +14 -0
- package/src/harness/given.tsx +212 -0
- package/src/index.ts +44 -0
- package/src/mock-backend/BrowserStateCfg.ts +35 -0
- package/src/mock-backend/MockBackendConfigurer.ts +97 -0
- package/src/mock-backend/MockDb.ts +66 -0
- package/src/mock-backend/MockService.ts +18 -0
- package/src/mock-backend/MockUserContext.ts +42 -0
- package/src/mock-backend/magazines.ts +245 -0
- package/src/mock-network/NetworkCatalog.ts +30 -0
- package/src/mock-network/NetworkConfigurerCfg.ts +24 -0
- package/src/mock-network/SharedNetwork.ts +100 -0
- package/src/mock-network/errorFactories.ts +28 -0
- package/src/mock-network/makeNetworkReplyWithCatalog.ts +19 -0
- package/src/setup/polyfillLocalStorage.ts +34 -0
- package/src/setup/svgMock.tsx +3 -0
- package/src/setup/testSetup.ts +58 -0
- package/src/tools/AppDetailTools.ts +240 -0
- package/src/tools/BrowserState.ts +41 -0
- package/src/tools/CatalogTools.ts +254 -0
- package/src/tools/ErrorTools.ts +49 -0
- package/src/tools/GalleryTools.ts +26 -0
- package/src/tools/suppressConsole.ts +66 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const defaultUser = {
|
|
2
|
+
name: "Test User",
|
|
3
|
+
isAdmin: false,
|
|
4
|
+
isAuthenticated: true
|
|
5
|
+
};
|
|
6
|
+
class MockUserContext {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.user = { ...defaultUser };
|
|
9
|
+
}
|
|
10
|
+
setUser(overrides) {
|
|
11
|
+
this.user = { ...this.user, ...overrides };
|
|
12
|
+
}
|
|
13
|
+
getUser() {
|
|
14
|
+
return this.user;
|
|
15
|
+
}
|
|
16
|
+
getSessionResponse() {
|
|
17
|
+
if (!this.user.isAuthenticated) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
user: {
|
|
22
|
+
id: "test-user-id",
|
|
23
|
+
email: `${this.user.name.toLowerCase().replace(/\s/g, ".")}@test.com`,
|
|
24
|
+
name: this.user.name,
|
|
25
|
+
isAdmin: this.user.isAdmin
|
|
26
|
+
},
|
|
27
|
+
session: {
|
|
28
|
+
id: "test-session-id",
|
|
29
|
+
userId: "test-user-id",
|
|
30
|
+
expiresAt: new Date(Date.now() + 864e5).toISOString()
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export {
|
|
36
|
+
MockUserContext
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=MockUserContext.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MockUserContext.js","sources":["../../../src/mock-backend/MockUserContext.ts"],"sourcesContent":["export interface UserConfig {\n name: string\n isAdmin: boolean\n isAuthenticated: boolean\n}\n\nconst defaultUser: UserConfig = {\n name: 'Test User',\n isAdmin: false,\n isAuthenticated: true,\n}\n\nexport class MockUserContext {\n private user: UserConfig = { ...defaultUser }\n\n setUser(overrides: Partial<UserConfig>): void {\n this.user = { ...this.user, ...overrides }\n }\n\n getUser(): UserConfig {\n return this.user\n }\n\n getSessionResponse(): object | null {\n if (!this.user.isAuthenticated) {\n return null\n }\n return {\n user: {\n id: 'test-user-id',\n email: `${this.user.name.toLowerCase().replace(/\\s/g, '.')}@test.com`,\n name: this.user.name,\n isAdmin: this.user.isAdmin,\n },\n session: {\n id: 'test-session-id',\n userId: 'test-user-id',\n expiresAt: new Date(Date.now() + 86400000).toISOString(),\n },\n }\n }\n}\n"],"names":[],"mappings":"AAMA,MAAM,cAA0B;AAAA,EAC9B,MAAM;AAAA,EACN,SAAS;AAAA,EACT,iBAAiB;AACnB;AAEO,MAAM,gBAAgB;AAAA,EAAtB,cAAA;AACL,SAAQ,OAAmB,EAAE,GAAG,YAAA;AAAA,EAAY;AAAA,EAE5C,QAAQ,WAAsC;AAC5C,SAAK,OAAO,EAAE,GAAG,KAAK,MAAM,GAAG,UAAA;AAAA,EACjC;AAAA,EAEA,UAAsB;AACpB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,qBAAoC;AAClC,QAAI,CAAC,KAAK,KAAK,iBAAiB;AAC9B,aAAO;AAAA,IACT;AACA,WAAO;AAAA,MACL,MAAM;AAAA,QACJ,IAAI;AAAA,QACJ,OAAO,GAAG,KAAK,KAAK,KAAK,cAAc,QAAQ,OAAO,GAAG,CAAC;AAAA,QAC1D,MAAM,KAAK,KAAK;AAAA,QAChB,SAAS,KAAK,KAAK;AAAA,MAAA;AAAA,MAErB,SAAS;AAAA,QACP,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,WAAW,IAAI,KAAK,KAAK,QAAQ,KAAQ,EAAE,YAAA;AAAA,MAAY;AAAA,IACzD;AAAA,EAEJ;AACF;"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { MockBackendConfigurer } from './MockBackendConfigurer.js';
|
|
2
|
+
import { BrowserStateCfg } from './BrowserStateCfg.js';
|
|
3
|
+
import { NetworkConfigurerCfg } from '../mock-network/NetworkConfigurerCfg.js';
|
|
4
|
+
export interface ConfigurerContext {
|
|
5
|
+
backendCfg: MockBackendConfigurer;
|
|
6
|
+
browserStateCfg: BrowserStateCfg;
|
|
7
|
+
networkCfg: NetworkConfigurerCfg;
|
|
8
|
+
}
|
|
9
|
+
/** A magazine is a function that configures the context */
|
|
10
|
+
export type Magazine = (ctx: ConfigurerContext) => void;
|
|
11
|
+
export declare const magazine: {
|
|
12
|
+
/** No apps, no tags, no approval methods */
|
|
13
|
+
empty: () => void;
|
|
14
|
+
/** 1 app (TaskFlow), 1 tag, 1 approval method — accepts optional post-configurer */
|
|
15
|
+
single: (postConfigure?: Magazine) => Magazine;
|
|
16
|
+
/** Full catalog, first-time user — accepts optional post-configurer */
|
|
17
|
+
full: (postConfigure?: Magazine) => Magazine;
|
|
18
|
+
/** Full catalog, returning user (cache + onboarding dismissed) — accepts optional post-configurer */
|
|
19
|
+
fullReturningUser: (postConfigure?: Magazine) => Magazine;
|
|
20
|
+
/** Inline custom configurer — pass-through for ad-hoc tests */
|
|
21
|
+
custom: (fn: Magazine) => Magazine;
|
|
22
|
+
};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
function fullMagazine(features, postConfigure) {
|
|
2
|
+
return (ctx) => {
|
|
3
|
+
const { backendCfg, browserStateCfg } = ctx;
|
|
4
|
+
const supportPortal = backendCfg.withApprovalMethod({
|
|
5
|
+
slug: "it-support",
|
|
6
|
+
type: "service",
|
|
7
|
+
displayName: "Support Portal",
|
|
8
|
+
config: {
|
|
9
|
+
url: "https://support.example.com",
|
|
10
|
+
description: "General IT support desk"
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
backendCfg.withApprovalMethod({
|
|
14
|
+
slug: "ops-support",
|
|
15
|
+
type: "service",
|
|
16
|
+
displayName: "Ops Portal",
|
|
17
|
+
config: { url: "https://opsdesk.example.com" }
|
|
18
|
+
});
|
|
19
|
+
const managerApproval = backendCfg.withApprovalMethod({
|
|
20
|
+
slug: "manager-approval",
|
|
21
|
+
type: "custom",
|
|
22
|
+
displayName: "Manager Approval",
|
|
23
|
+
config: {}
|
|
24
|
+
});
|
|
25
|
+
backendCfg.withApprovalMethod({
|
|
26
|
+
slug: "self-service",
|
|
27
|
+
type: "custom",
|
|
28
|
+
displayName: "Self-Service",
|
|
29
|
+
config: {}
|
|
30
|
+
});
|
|
31
|
+
backendCfg.withTag({
|
|
32
|
+
prefix: "category",
|
|
33
|
+
displayName: "Category",
|
|
34
|
+
description: "Application category",
|
|
35
|
+
values: [
|
|
36
|
+
{
|
|
37
|
+
value: "project-management",
|
|
38
|
+
displayName: "Project Management",
|
|
39
|
+
description: ""
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
value: "communication",
|
|
43
|
+
displayName: "Communication",
|
|
44
|
+
description: ""
|
|
45
|
+
},
|
|
46
|
+
{ value: "internal", displayName: "Internal Tools", description: "" }
|
|
47
|
+
]
|
|
48
|
+
});
|
|
49
|
+
backendCfg.withTag({
|
|
50
|
+
prefix: "team",
|
|
51
|
+
displayName: "Team",
|
|
52
|
+
description: "Owning team",
|
|
53
|
+
values: [
|
|
54
|
+
{ value: "engineering", displayName: "Engineering", description: "" },
|
|
55
|
+
{ value: "platform", displayName: "Platform", description: "" }
|
|
56
|
+
]
|
|
57
|
+
});
|
|
58
|
+
backendCfg.withApp({
|
|
59
|
+
slug: "taskflow",
|
|
60
|
+
displayName: "TaskFlow",
|
|
61
|
+
description: "Project tracking and issue management",
|
|
62
|
+
tags: ["category:project-management", "team:engineering"],
|
|
63
|
+
screenshotIds: ["ss-taskflow-1", "ss-taskflow-2", "ss-taskflow-3"],
|
|
64
|
+
appUrl: "https://taskflow.example.com",
|
|
65
|
+
accessRequest: {
|
|
66
|
+
approvalMethodSlug: supportPortal.slug,
|
|
67
|
+
comments: "Submit a ticket"
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
backendCfg.withApp({
|
|
71
|
+
slug: "teamchat",
|
|
72
|
+
displayName: "TeamChat",
|
|
73
|
+
description: "Team messaging and collaboration",
|
|
74
|
+
tags: ["category:communication", "team:platform"],
|
|
75
|
+
screenshotIds: ["ss-teamchat-1"],
|
|
76
|
+
appUrl: "https://teamchat.example.com"
|
|
77
|
+
});
|
|
78
|
+
const newTool = backendCfg.withApp({
|
|
79
|
+
slug: "new-tool",
|
|
80
|
+
displayName: "New Tool",
|
|
81
|
+
description: "The modern replacement",
|
|
82
|
+
tags: ["category:internal"]
|
|
83
|
+
});
|
|
84
|
+
backendCfg.withApp({
|
|
85
|
+
slug: "old-tool",
|
|
86
|
+
displayName: "Old Tool",
|
|
87
|
+
description: "Legacy project tracker",
|
|
88
|
+
tags: ["category:project-management"],
|
|
89
|
+
deprecated: {
|
|
90
|
+
type: "deprecated",
|
|
91
|
+
comment: "Replaced by New Tool",
|
|
92
|
+
replacementSlug: newTool.slug
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
backendCfg.withApp({
|
|
96
|
+
slug: "legacy-app",
|
|
97
|
+
displayName: "Legacy App",
|
|
98
|
+
description: "Still works but discouraged",
|
|
99
|
+
tags: ["category:internal"],
|
|
100
|
+
deprecated: {
|
|
101
|
+
type: "discouraged",
|
|
102
|
+
comment: "Consider using alternatives"
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
backendCfg.withApp({
|
|
106
|
+
slug: "internal-portal",
|
|
107
|
+
displayName: "Internal Portal",
|
|
108
|
+
description: "Company intranet portal",
|
|
109
|
+
tags: ["category:internal", "team:platform"],
|
|
110
|
+
screenshotIds: ["ss-portal-1"],
|
|
111
|
+
accessRequest: {
|
|
112
|
+
approvalMethodSlug: managerApproval.slug,
|
|
113
|
+
comments: "Requires manager approval"
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
backendCfg.withApp({
|
|
117
|
+
slug: "admin-console",
|
|
118
|
+
displayName: "Admin Console",
|
|
119
|
+
description: "System administration dashboard",
|
|
120
|
+
tags: ["category:internal", "team:engineering"]
|
|
121
|
+
});
|
|
122
|
+
if (features.dismissOnboarding) {
|
|
123
|
+
browserStateCfg.dismissOnboarding();
|
|
124
|
+
}
|
|
125
|
+
if (features.prepopulateCache) {
|
|
126
|
+
browserStateCfg.withOfflineData();
|
|
127
|
+
}
|
|
128
|
+
postConfigure == null ? void 0 : postConfigure(ctx);
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function singleMagazine(postConfigure) {
|
|
132
|
+
return (ctx) => {
|
|
133
|
+
const { backendCfg } = ctx;
|
|
134
|
+
const method = backendCfg.withApprovalMethod({
|
|
135
|
+
slug: "it-support",
|
|
136
|
+
type: "service",
|
|
137
|
+
displayName: "Support Portal",
|
|
138
|
+
config: { url: "https://support.example.com" }
|
|
139
|
+
});
|
|
140
|
+
backendCfg.withTag({
|
|
141
|
+
prefix: "category",
|
|
142
|
+
displayName: "Category",
|
|
143
|
+
description: "Application category",
|
|
144
|
+
values: [
|
|
145
|
+
{
|
|
146
|
+
value: "project-management",
|
|
147
|
+
displayName: "Project Management",
|
|
148
|
+
description: ""
|
|
149
|
+
}
|
|
150
|
+
]
|
|
151
|
+
});
|
|
152
|
+
backendCfg.withApp({
|
|
153
|
+
slug: "taskflow",
|
|
154
|
+
displayName: "TaskFlow",
|
|
155
|
+
description: "Project tracking and issue management",
|
|
156
|
+
tags: ["category:project-management"],
|
|
157
|
+
screenshotIds: ["screenshot-taskflow-1", "screenshot-taskflow-2"],
|
|
158
|
+
appUrl: "https://taskflow.example.com",
|
|
159
|
+
accessRequest: {
|
|
160
|
+
approvalMethodSlug: method.slug,
|
|
161
|
+
comments: "Submit a ticket to IT"
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
postConfigure == null ? void 0 : postConfigure(ctx);
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
const magazine = {
|
|
168
|
+
/** No apps, no tags, no approval methods */
|
|
169
|
+
empty: (() => {
|
|
170
|
+
}),
|
|
171
|
+
/** 1 app (TaskFlow), 1 tag, 1 approval method — accepts optional post-configurer */
|
|
172
|
+
single: (postConfigure) => singleMagazine(postConfigure),
|
|
173
|
+
/** Full catalog, first-time user — accepts optional post-configurer */
|
|
174
|
+
full: (postConfigure) => fullMagazine({}, postConfigure),
|
|
175
|
+
/** Full catalog, returning user (cache + onboarding dismissed) — accepts optional post-configurer */
|
|
176
|
+
fullReturningUser: (postConfigure) => fullMagazine(
|
|
177
|
+
{ prepopulateCache: true, dismissOnboarding: true },
|
|
178
|
+
postConfigure
|
|
179
|
+
),
|
|
180
|
+
/** Inline custom configurer — pass-through for ad-hoc tests */
|
|
181
|
+
custom: (fn) => fn
|
|
182
|
+
};
|
|
183
|
+
export {
|
|
184
|
+
magazine
|
|
185
|
+
};
|
|
186
|
+
//# sourceMappingURL=magazines.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"magazines.js","sources":["../../../src/mock-backend/magazines.ts"],"sourcesContent":["import type { MockBackendConfigurer } from './MockBackendConfigurer'\nimport type { BrowserStateCfg } from './BrowserStateCfg'\nimport type { NetworkConfigurerCfg } from '../mock-network/NetworkConfigurerCfg'\n\n// ---------------------------------------------------------------------------\n// Core types\n// ---------------------------------------------------------------------------\n\nexport interface ConfigurerContext {\n backendCfg: MockBackendConfigurer\n browserStateCfg: BrowserStateCfg\n networkCfg: NetworkConfigurerCfg\n}\n\n/** A magazine is a function that configures the context */\nexport type Magazine = (ctx: ConfigurerContext) => void\n\n// ---------------------------------------------------------------------------\n// Full magazine features\n// ---------------------------------------------------------------------------\n\ninterface FullFeatures {\n prepopulateCache?: boolean\n dismissOnboarding?: boolean\n}\n\nfunction fullMagazine(\n features: FullFeatures,\n postConfigure?: Magazine,\n): Magazine {\n return (ctx) => {\n const { backendCfg, browserStateCfg } = ctx\n\n // Approval methods — all 3 types\n const supportPortal = backendCfg.withApprovalMethod({\n slug: 'it-support',\n type: 'service',\n displayName: 'Support Portal',\n config: {\n url: 'https://support.example.com',\n description: 'General IT support desk',\n },\n })\n backendCfg.withApprovalMethod({\n slug: 'ops-support',\n type: 'service',\n displayName: 'Ops Portal',\n config: { url: 'https://opsdesk.example.com' },\n })\n const managerApproval = backendCfg.withApprovalMethod({\n slug: 'manager-approval',\n type: 'custom',\n displayName: 'Manager Approval',\n config: {},\n })\n backendCfg.withApprovalMethod({\n slug: 'self-service',\n type: 'custom',\n displayName: 'Self-Service',\n config: {},\n })\n\n // Tag definitions\n backendCfg.withTag({\n prefix: 'category',\n displayName: 'Category',\n description: 'Application category',\n values: [\n {\n value: 'project-management',\n displayName: 'Project Management',\n description: '',\n },\n {\n value: 'communication',\n displayName: 'Communication',\n description: '',\n },\n { value: 'internal', displayName: 'Internal Tools', description: '' },\n ],\n })\n backendCfg.withTag({\n prefix: 'team',\n displayName: 'Team',\n description: 'Owning team',\n values: [\n { value: 'engineering', displayName: 'Engineering', description: '' },\n { value: 'platform', displayName: 'Platform', description: '' },\n ],\n })\n\n // Apps\n backendCfg.withApp({\n slug: 'taskflow',\n displayName: 'TaskFlow',\n description: 'Project tracking and issue management',\n tags: ['category:project-management', 'team:engineering'],\n screenshotIds: ['ss-taskflow-1', 'ss-taskflow-2', 'ss-taskflow-3'],\n appUrl: 'https://taskflow.example.com',\n accessRequest: {\n approvalMethodSlug: supportPortal.slug,\n comments: 'Submit a ticket',\n },\n })\n\n backendCfg.withApp({\n slug: 'teamchat',\n displayName: 'TeamChat',\n description: 'Team messaging and collaboration',\n tags: ['category:communication', 'team:platform'],\n screenshotIds: ['ss-teamchat-1'],\n appUrl: 'https://teamchat.example.com',\n })\n\n const newTool = backendCfg.withApp({\n slug: 'new-tool',\n displayName: 'New Tool',\n description: 'The modern replacement',\n tags: ['category:internal'],\n })\n\n backendCfg.withApp({\n slug: 'old-tool',\n displayName: 'Old Tool',\n description: 'Legacy project tracker',\n tags: ['category:project-management'],\n deprecated: {\n type: 'deprecated',\n comment: 'Replaced by New Tool',\n replacementSlug: newTool.slug,\n },\n })\n\n backendCfg.withApp({\n slug: 'legacy-app',\n displayName: 'Legacy App',\n description: 'Still works but discouraged',\n tags: ['category:internal'],\n deprecated: {\n type: 'discouraged',\n comment: 'Consider using alternatives',\n },\n })\n\n backendCfg.withApp({\n slug: 'internal-portal',\n displayName: 'Internal Portal',\n description: 'Company intranet portal',\n tags: ['category:internal', 'team:platform'],\n screenshotIds: ['ss-portal-1'],\n accessRequest: {\n approvalMethodSlug: managerApproval.slug,\n comments: 'Requires manager approval',\n },\n })\n\n backendCfg.withApp({\n slug: 'admin-console',\n displayName: 'Admin Console',\n description: 'System administration dashboard',\n tags: ['category:internal', 'team:engineering'],\n })\n\n // Apply features\n if (features.dismissOnboarding) {\n browserStateCfg.dismissOnboarding()\n }\n if (features.prepopulateCache) {\n browserStateCfg.withOfflineData()\n }\n\n // Apply post-configurer (for test-specific overrides)\n postConfigure?.(ctx)\n }\n}\n\n// ---------------------------------------------------------------------------\n// Single magazine\n// ---------------------------------------------------------------------------\n\nfunction singleMagazine(postConfigure?: Magazine): Magazine {\n return (ctx) => {\n const { backendCfg } = ctx\n\n const method = backendCfg.withApprovalMethod({\n slug: 'it-support',\n type: 'service',\n displayName: 'Support Portal',\n config: { url: 'https://support.example.com' },\n })\n\n backendCfg.withTag({\n prefix: 'category',\n displayName: 'Category',\n description: 'Application category',\n values: [\n {\n value: 'project-management',\n displayName: 'Project Management',\n description: '',\n },\n ],\n })\n\n backendCfg.withApp({\n slug: 'taskflow',\n displayName: 'TaskFlow',\n description: 'Project tracking and issue management',\n tags: ['category:project-management'],\n screenshotIds: ['screenshot-taskflow-1', 'screenshot-taskflow-2'],\n appUrl: 'https://taskflow.example.com',\n accessRequest: {\n approvalMethodSlug: method.slug,\n comments: 'Submit a ticket to IT',\n },\n })\n\n postConfigure?.(ctx)\n }\n}\n\n// ---------------------------------------------------------------------------\n// Magazine entries\n// ---------------------------------------------------------------------------\n\nexport const magazine = {\n /** No apps, no tags, no approval methods */\n empty: (() => {}) satisfies Magazine,\n\n /** 1 app (TaskFlow), 1 tag, 1 approval method — accepts optional post-configurer */\n single: (postConfigure?: Magazine) => singleMagazine(postConfigure),\n\n /** Full catalog, first-time user — accepts optional post-configurer */\n full: (postConfigure?: Magazine) => fullMagazine({}, postConfigure),\n\n /** Full catalog, returning user (cache + onboarding dismissed) — accepts optional post-configurer */\n fullReturningUser: (postConfigure?: Magazine) =>\n fullMagazine(\n { prepopulateCache: true, dismissOnboarding: true },\n postConfigure,\n ),\n\n /** Inline custom configurer — pass-through for ad-hoc tests */\n custom: (fn: Magazine): Magazine => fn,\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { RequestHandler } from 'msw';
|
|
2
|
+
export interface NetworkInterceptor {
|
|
3
|
+
scopeKey: string[];
|
|
4
|
+
handler: RequestHandler;
|
|
5
|
+
}
|
|
6
|
+
export declare class NetworkCatalog {
|
|
7
|
+
private interceptors;
|
|
8
|
+
add(interceptor: NetworkInterceptor): void;
|
|
9
|
+
replace(scopeKey: string[], handler: RequestHandler): void;
|
|
10
|
+
getHandlers(): RequestHandler[];
|
|
11
|
+
private keysMatch;
|
|
12
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
class NetworkCatalog {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.interceptors = [];
|
|
4
|
+
}
|
|
5
|
+
add(interceptor) {
|
|
6
|
+
this.interceptors.push(interceptor);
|
|
7
|
+
}
|
|
8
|
+
replace(scopeKey, handler) {
|
|
9
|
+
this.interceptors = this.interceptors.filter(
|
|
10
|
+
(i) => !this.keysMatch(i.scopeKey, scopeKey)
|
|
11
|
+
);
|
|
12
|
+
this.interceptors.push({ scopeKey, handler });
|
|
13
|
+
}
|
|
14
|
+
getHandlers() {
|
|
15
|
+
return this.interceptors.map((i) => i.handler);
|
|
16
|
+
}
|
|
17
|
+
keysMatch(existing, search) {
|
|
18
|
+
return search.every((k) => existing.includes(k));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export {
|
|
22
|
+
NetworkCatalog
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=NetworkCatalog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NetworkCatalog.js","sources":["../../../src/mock-network/NetworkCatalog.ts"],"sourcesContent":["import type { RequestHandler } from 'msw'\n\nexport interface NetworkInterceptor {\n scopeKey: string[]\n handler: RequestHandler\n}\n\nexport class NetworkCatalog {\n private interceptors: NetworkInterceptor[] = []\n\n add(interceptor: NetworkInterceptor): void {\n this.interceptors.push(interceptor)\n }\n\n replace(scopeKey: string[], handler: RequestHandler): void {\n // Remove existing interceptor with matching key, add new one\n this.interceptors = this.interceptors.filter(\n (i) => !this.keysMatch(i.scopeKey, scopeKey),\n )\n this.interceptors.push({ scopeKey, handler })\n }\n\n getHandlers(): RequestHandler[] {\n return this.interceptors.map((i) => i.handler)\n }\n\n private keysMatch(existing: string[], search: string[]): boolean {\n return search.every((k) => existing.includes(k))\n }\n}\n"],"names":[],"mappings":"AAOO,MAAM,eAAe;AAAA,EAArB,cAAA;AACL,SAAQ,eAAqC,CAAA;AAAA,EAAC;AAAA,EAE9C,IAAI,aAAuC;AACzC,SAAK,aAAa,KAAK,WAAW;AAAA,EACpC;AAAA,EAEA,QAAQ,UAAoB,SAA+B;AAEzD,SAAK,eAAe,KAAK,aAAa;AAAA,MACpC,CAAC,MAAM,CAAC,KAAK,UAAU,EAAE,UAAU,QAAQ;AAAA,IAAA;AAE7C,SAAK,aAAa,KAAK,EAAE,UAAU,SAAS;AAAA,EAC9C;AAAA,EAEA,cAAgC;AAC9B,WAAO,KAAK,aAAa,IAAI,CAAC,MAAM,EAAE,OAAO;AAAA,EAC/C;AAAA,EAEQ,UAAU,UAAoB,QAA2B;AAC/D,WAAO,OAAO,MAAM,CAAC,MAAM,SAAS,SAAS,CAAC,CAAC;AAAA,EACjD;AACF;"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { NetworkCatalog } from './NetworkCatalog.js';
|
|
2
|
+
import { MockService } from '../mock-backend/MockService.js';
|
|
3
|
+
export type NetworkOverrideFn = (catalog: NetworkCatalog, service: MockService) => void;
|
|
4
|
+
/**
|
|
5
|
+
* Data-carrier that collects network override callbacks.
|
|
6
|
+
* Does not execute anything — `given()` applies overrides after building the default catalog.
|
|
7
|
+
*/
|
|
8
|
+
export declare class NetworkConfigurerCfg {
|
|
9
|
+
private _overrides;
|
|
10
|
+
/** Register a callback to modify the network catalog after default handlers are set up */
|
|
11
|
+
overrideConfig(fn: NetworkOverrideFn): void;
|
|
12
|
+
get overrides(): NetworkOverrideFn[];
|
|
13
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class NetworkConfigurerCfg {
|
|
2
|
+
constructor() {
|
|
3
|
+
this._overrides = [];
|
|
4
|
+
}
|
|
5
|
+
/** Register a callback to modify the network catalog after default handlers are set up */
|
|
6
|
+
overrideConfig(fn) {
|
|
7
|
+
this._overrides.push(fn);
|
|
8
|
+
}
|
|
9
|
+
get overrides() {
|
|
10
|
+
return this._overrides;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
NetworkConfigurerCfg
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=NetworkConfigurerCfg.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NetworkConfigurerCfg.js","sources":["../../../src/mock-network/NetworkConfigurerCfg.ts"],"sourcesContent":["import type { NetworkCatalog } from './NetworkCatalog'\nimport type { MockService } from '../mock-backend/MockService'\n\nexport type NetworkOverrideFn = (\n catalog: NetworkCatalog,\n service: MockService,\n) => void\n\n/**\n * Data-carrier that collects network override callbacks.\n * Does not execute anything — `given()` applies overrides after building the default catalog.\n */\nexport class NetworkConfigurerCfg {\n private _overrides: NetworkOverrideFn[] = []\n\n /** Register a callback to modify the network catalog after default handlers are set up */\n overrideConfig(fn: NetworkOverrideFn): void {\n this._overrides.push(fn)\n }\n\n get overrides(): NetworkOverrideFn[] {\n return this._overrides\n }\n}\n"],"names":[],"mappings":"AAYO,MAAM,qBAAqB;AAAA,EAA3B,cAAA;AACL,SAAQ,aAAkC,CAAA;AAAA,EAAC;AAAA;AAAA,EAG3C,eAAe,IAA6B;AAC1C,SAAK,WAAW,KAAK,EAAE;AAAA,EACzB;AAAA,EAEA,IAAI,YAAiC;AACnC,WAAO,KAAK;AAAA,EACd;AACF;"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { MockService } from '../mock-backend/MockService.js';
|
|
2
|
+
import { NetworkInterceptor } from './NetworkCatalog.js';
|
|
3
|
+
export declare const SharedNetwork: {
|
|
4
|
+
appCatalogQuery(service: MockService): NetworkInterceptor;
|
|
5
|
+
authGetSession(service: MockService): NetworkInterceptor;
|
|
6
|
+
authGetProviders(overrides?: {
|
|
7
|
+
devLoginEnabled?: boolean;
|
|
8
|
+
}): NetworkInterceptor;
|
|
9
|
+
authSignOut(): NetworkInterceptor;
|
|
10
|
+
authDevLogout(): NetworkInterceptor;
|
|
11
|
+
screenshotBinary(): NetworkInterceptor;
|
|
12
|
+
iconBinary(): NetworkInterceptor;
|
|
13
|
+
};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { http, HttpResponse } from "msw";
|
|
2
|
+
import { createTRPCMsw, httpLink } from "msw-trpc";
|
|
3
|
+
const trpcMsw = createTRPCMsw({
|
|
4
|
+
links: [httpLink({ url: "/api/trpc" })]
|
|
5
|
+
});
|
|
6
|
+
const PLACEHOLDER_PNG = new Uint8Array([
|
|
7
|
+
137,
|
|
8
|
+
80,
|
|
9
|
+
78,
|
|
10
|
+
71,
|
|
11
|
+
13,
|
|
12
|
+
10,
|
|
13
|
+
26,
|
|
14
|
+
10,
|
|
15
|
+
0,
|
|
16
|
+
0,
|
|
17
|
+
0,
|
|
18
|
+
13,
|
|
19
|
+
73,
|
|
20
|
+
72,
|
|
21
|
+
68,
|
|
22
|
+
82,
|
|
23
|
+
0,
|
|
24
|
+
0,
|
|
25
|
+
0,
|
|
26
|
+
1,
|
|
27
|
+
0,
|
|
28
|
+
0,
|
|
29
|
+
0,
|
|
30
|
+
1,
|
|
31
|
+
8,
|
|
32
|
+
6,
|
|
33
|
+
0,
|
|
34
|
+
0,
|
|
35
|
+
0,
|
|
36
|
+
31,
|
|
37
|
+
21,
|
|
38
|
+
196,
|
|
39
|
+
137,
|
|
40
|
+
0,
|
|
41
|
+
0,
|
|
42
|
+
0,
|
|
43
|
+
10,
|
|
44
|
+
73,
|
|
45
|
+
68,
|
|
46
|
+
65,
|
|
47
|
+
84,
|
|
48
|
+
120,
|
|
49
|
+
156,
|
|
50
|
+
98,
|
|
51
|
+
0,
|
|
52
|
+
0,
|
|
53
|
+
0,
|
|
54
|
+
2,
|
|
55
|
+
0,
|
|
56
|
+
1,
|
|
57
|
+
229,
|
|
58
|
+
39,
|
|
59
|
+
222,
|
|
60
|
+
252,
|
|
61
|
+
0,
|
|
62
|
+
0,
|
|
63
|
+
0,
|
|
64
|
+
0,
|
|
65
|
+
73,
|
|
66
|
+
69,
|
|
67
|
+
78,
|
|
68
|
+
68,
|
|
69
|
+
174,
|
|
70
|
+
66,
|
|
71
|
+
96,
|
|
72
|
+
130
|
|
73
|
+
]);
|
|
74
|
+
const SharedNetwork = {
|
|
75
|
+
appCatalogQuery(service) {
|
|
76
|
+
return {
|
|
77
|
+
scopeKey: ["appCatalog-query"],
|
|
78
|
+
handler: trpcMsw.appCatalog.getData.query(() => {
|
|
79
|
+
return service.getAppCatalogData();
|
|
80
|
+
})
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
authGetSession(service) {
|
|
84
|
+
return {
|
|
85
|
+
scopeKey: ["auth-session"],
|
|
86
|
+
handler: http.get(/\/api\/auth\/session/, () => {
|
|
87
|
+
const session = service.getSessionResponse();
|
|
88
|
+
if (!session) {
|
|
89
|
+
return HttpResponse.json(
|
|
90
|
+
{ error: "Not authenticated" },
|
|
91
|
+
{ status: 401 }
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
return HttpResponse.json(session);
|
|
95
|
+
})
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
authGetProviders(overrides) {
|
|
99
|
+
return {
|
|
100
|
+
scopeKey: ["auth-providers"],
|
|
101
|
+
handler: trpcMsw.auth.getProviders.query(() => {
|
|
102
|
+
return {
|
|
103
|
+
providers: [],
|
|
104
|
+
devLoginEnabled: (overrides == null ? void 0 : overrides.devLoginEnabled) ?? false
|
|
105
|
+
};
|
|
106
|
+
})
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
authSignOut() {
|
|
110
|
+
return {
|
|
111
|
+
scopeKey: ["auth-sign-out"],
|
|
112
|
+
handler: http.post(/\/api\/auth\/sign-out/, () => {
|
|
113
|
+
return HttpResponse.json({ ok: true });
|
|
114
|
+
})
|
|
115
|
+
};
|
|
116
|
+
},
|
|
117
|
+
authDevLogout() {
|
|
118
|
+
return {
|
|
119
|
+
scopeKey: ["auth-dev-logout"],
|
|
120
|
+
handler: http.post(/\/api\/auth\/dev-logout/, () => {
|
|
121
|
+
return HttpResponse.json({ ok: true });
|
|
122
|
+
})
|
|
123
|
+
};
|
|
124
|
+
},
|
|
125
|
+
screenshotBinary() {
|
|
126
|
+
return {
|
|
127
|
+
scopeKey: ["screenshot"],
|
|
128
|
+
handler: http.get(/\/api\/screenshots\/.*/, () => {
|
|
129
|
+
return new HttpResponse(PLACEHOLDER_PNG, {
|
|
130
|
+
headers: { "Content-Type": "image/png" }
|
|
131
|
+
});
|
|
132
|
+
})
|
|
133
|
+
};
|
|
134
|
+
},
|
|
135
|
+
iconBinary() {
|
|
136
|
+
return {
|
|
137
|
+
scopeKey: ["icon"],
|
|
138
|
+
handler: http.get(/\/api\/icons\/.*/, () => {
|
|
139
|
+
return new HttpResponse(PLACEHOLDER_PNG, {
|
|
140
|
+
headers: { "Content-Type": "image/png" }
|
|
141
|
+
});
|
|
142
|
+
})
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
export {
|
|
147
|
+
SharedNetwork
|
|
148
|
+
};
|
|
149
|
+
//# sourceMappingURL=SharedNetwork.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SharedNetwork.js","sources":["../../../src/mock-network/SharedNetwork.ts"],"sourcesContent":["import { HttpResponse, http } from 'msw'\nimport { createTRPCMsw, httpLink } from 'msw-trpc'\nimport type { TRPCRouter } from '@igstack/app-catalog-backend-core'\nimport type { MockService } from '../mock-backend/MockService'\nimport type { NetworkInterceptor } from './NetworkCatalog'\n\nconst trpcMsw = createTRPCMsw<TRPCRouter>({\n links: [httpLink({ url: '/api/trpc' })],\n})\n\n// 1x1 transparent PNG placeholder\nconst PLACEHOLDER_PNG = new Uint8Array([\n 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49,\n 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06,\n 0x00, 0x00, 0x00, 0x1f, 0x15, 0xc4, 0x89, 0x00, 0x00, 0x00, 0x0a, 0x49, 0x44,\n 0x41, 0x54, 0x78, 0x9c, 0x62, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27,\n 0xde, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60,\n 0x82,\n])\n\nexport const SharedNetwork = {\n appCatalogQuery(service: MockService): NetworkInterceptor {\n return {\n scopeKey: ['appCatalog-query'],\n handler: trpcMsw.appCatalog.getData.query(() => {\n return service.getAppCatalogData()\n }),\n }\n },\n\n authGetSession(service: MockService): NetworkInterceptor {\n return {\n scopeKey: ['auth-session'],\n handler: http.get(/\\/api\\/auth\\/session/, () => {\n const session = service.getSessionResponse()\n if (!session) {\n return HttpResponse.json(\n { error: 'Not authenticated' },\n { status: 401 },\n )\n }\n return HttpResponse.json(session)\n }),\n }\n },\n\n authGetProviders(overrides?: {\n devLoginEnabled?: boolean\n }): NetworkInterceptor {\n return {\n scopeKey: ['auth-providers'],\n handler: trpcMsw.auth.getProviders.query(() => {\n return {\n providers: [] as string[],\n devLoginEnabled: overrides?.devLoginEnabled ?? false,\n }\n }),\n }\n },\n\n authSignOut(): NetworkInterceptor {\n return {\n scopeKey: ['auth-sign-out'],\n handler: http.post(/\\/api\\/auth\\/sign-out/, () => {\n return HttpResponse.json({ ok: true })\n }),\n }\n },\n\n authDevLogout(): NetworkInterceptor {\n return {\n scopeKey: ['auth-dev-logout'],\n handler: http.post(/\\/api\\/auth\\/dev-logout/, () => {\n return HttpResponse.json({ ok: true })\n }),\n }\n },\n\n screenshotBinary(): NetworkInterceptor {\n return {\n scopeKey: ['screenshot'],\n handler: http.get(/\\/api\\/screenshots\\/.*/, () => {\n return new HttpResponse(PLACEHOLDER_PNG, {\n headers: { 'Content-Type': 'image/png' },\n })\n }),\n }\n },\n\n iconBinary(): NetworkInterceptor {\n return {\n scopeKey: ['icon'],\n handler: http.get(/\\/api\\/icons\\/.*/, () => {\n return new HttpResponse(PLACEHOLDER_PNG, {\n headers: { 'Content-Type': 'image/png' },\n })\n }),\n }\n },\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { RequestHandler } from 'msw';
|
|
2
|
+
/**
|
|
3
|
+
* Simulates a network-level connection reset error.
|
|
4
|
+
* This is what happens when the backend is completely unreachable.
|
|
5
|
+
*/
|
|
6
|
+
export declare function makeConnectionResetError(): RequestHandler;
|
|
7
|
+
/**
|
|
8
|
+
* Simulates a reverse proxy returning HTML instead of JSON
|
|
9
|
+
* (e.g., nginx 502 Bad Gateway page).
|
|
10
|
+
*/
|
|
11
|
+
export declare function makeHtmlResponseError(): RequestHandler;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { http, HttpResponse } from "msw";
|
|
2
|
+
function makeConnectionResetError() {
|
|
3
|
+
return http.get(/\/api\/trpc\/appCatalog\.getData/, () => {
|
|
4
|
+
return HttpResponse.error();
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
function makeHtmlResponseError() {
|
|
8
|
+
return http.get(/\/api\/trpc\/appCatalog\.getData/, () => {
|
|
9
|
+
return new HttpResponse(
|
|
10
|
+
"<html><head><title>502 Bad Gateway</title></head><body><h1>502 Bad Gateway</h1><p>The server is temporarily unable to service your request.</p></body></html>",
|
|
11
|
+
{
|
|
12
|
+
status: 502,
|
|
13
|
+
headers: { "Content-Type": "text/html" }
|
|
14
|
+
}
|
|
15
|
+
);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
export {
|
|
19
|
+
makeConnectionResetError,
|
|
20
|
+
makeHtmlResponseError
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=errorFactories.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errorFactories.js","sources":["../../../src/mock-network/errorFactories.ts"],"sourcesContent":["import { HttpResponse, http } from 'msw'\nimport type { RequestHandler } from 'msw'\n\n/**\n * Simulates a network-level connection reset error.\n * This is what happens when the backend is completely unreachable.\n */\nexport function makeConnectionResetError(): RequestHandler {\n return http.get(/\\/api\\/trpc\\/appCatalog\\.getData/, () => {\n return HttpResponse.error()\n })\n}\n\n/**\n * Simulates a reverse proxy returning HTML instead of JSON\n * (e.g., nginx 502 Bad Gateway page).\n */\nexport function makeHtmlResponseError(): RequestHandler {\n return http.get(/\\/api\\/trpc\\/appCatalog\\.getData/, () => {\n return new HttpResponse(\n '<html><head><title>502 Bad Gateway</title></head><body><h1>502 Bad Gateway</h1><p>The server is temporarily unable to service your request.</p></body></html>',\n {\n status: 502,\n headers: { 'Content-Type': 'text/html' },\n },\n )\n })\n}\n"],"names":[],"mappings":";AAOO,SAAS,2BAA2C;AACzD,SAAO,KAAK,IAAI,oCAAoC,MAAM;AACxD,WAAO,aAAa,MAAA;AAAA,EACtB,CAAC;AACH;AAMO,SAAS,wBAAwC;AACtD,SAAO,KAAK,IAAI,oCAAoC,MAAM;AACxD,WAAO,IAAI;AAAA,MACT;AAAA,MACA;AAAA,QACE,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,YAAA;AAAA,MAAY;AAAA,IACzC;AAAA,EAEJ,CAAC;AACH;"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { NetworkCatalog } from "./NetworkCatalog.js";
|
|
2
|
+
import { SharedNetwork } from "./SharedNetwork.js";
|
|
3
|
+
function makeNetworkReplyWithCatalog(service) {
|
|
4
|
+
const catalog = new NetworkCatalog();
|
|
5
|
+
catalog.add(SharedNetwork.appCatalogQuery(service));
|
|
6
|
+
catalog.add(SharedNetwork.authGetSession(service));
|
|
7
|
+
catalog.add(SharedNetwork.authGetProviders());
|
|
8
|
+
catalog.add(SharedNetwork.authSignOut());
|
|
9
|
+
catalog.add(SharedNetwork.authDevLogout());
|
|
10
|
+
catalog.add(SharedNetwork.screenshotBinary());
|
|
11
|
+
catalog.add(SharedNetwork.iconBinary());
|
|
12
|
+
return catalog;
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
makeNetworkReplyWithCatalog
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=makeNetworkReplyWithCatalog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"makeNetworkReplyWithCatalog.js","sources":["../../../src/mock-network/makeNetworkReplyWithCatalog.ts"],"sourcesContent":["import type { MockService } from '../mock-backend/MockService'\nimport { NetworkCatalog } from './NetworkCatalog'\nimport { SharedNetwork } from './SharedNetwork'\n\nexport function makeNetworkReplyWithCatalog(\n service: MockService,\n): NetworkCatalog {\n const catalog = new NetworkCatalog()\n\n catalog.add(SharedNetwork.appCatalogQuery(service))\n catalog.add(SharedNetwork.authGetSession(service))\n catalog.add(SharedNetwork.authGetProviders())\n catalog.add(SharedNetwork.authSignOut())\n catalog.add(SharedNetwork.authDevLogout())\n catalog.add(SharedNetwork.screenshotBinary())\n catalog.add(SharedNetwork.iconBinary())\n\n return catalog\n}\n"],"names":[],"mappings":";;AAIO,SAAS,4BACd,SACgB;AAChB,QAAM,UAAU,IAAI,eAAA;AAEpB,UAAQ,IAAI,cAAc,gBAAgB,OAAO,CAAC;AAClD,UAAQ,IAAI,cAAc,eAAe,OAAO,CAAC;AACjD,UAAQ,IAAI,cAAc,kBAAkB;AAC5C,UAAQ,IAAI,cAAc,aAAa;AACvC,UAAQ,IAAI,cAAc,eAAe;AACzC,UAAQ,IAAI,cAAc,kBAAkB;AAC5C,UAAQ,IAAI,cAAc,YAAY;AAEtC,SAAO;AACT;"}
|
|
File without changes
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
if (typeof globalThis.localStorage === "undefined" || typeof globalThis.localStorage.clear !== "function") {
|
|
2
|
+
const store = /* @__PURE__ */ new Map();
|
|
3
|
+
const storage = {
|
|
4
|
+
getItem: (key) => store.get(key) ?? null,
|
|
5
|
+
setItem: (key, value) => {
|
|
6
|
+
store.set(key, String(value));
|
|
7
|
+
},
|
|
8
|
+
removeItem: (key) => {
|
|
9
|
+
store.delete(key);
|
|
10
|
+
},
|
|
11
|
+
clear: () => {
|
|
12
|
+
store.clear();
|
|
13
|
+
},
|
|
14
|
+
get length() {
|
|
15
|
+
return store.size;
|
|
16
|
+
},
|
|
17
|
+
key: (index) => [...store.keys()][index] ?? null
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(globalThis, "localStorage", {
|
|
20
|
+
value: storage,
|
|
21
|
+
writable: true,
|
|
22
|
+
configurable: true
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=polyfillLocalStorage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"polyfillLocalStorage.js","sources":["../../../src/setup/polyfillLocalStorage.ts"],"sourcesContent":["/**\n * Polyfill localStorage for jsdom environments where it's missing or incomplete.\n *\n * jsdom only provides localStorage when initialized with a valid URL (not about:blank).\n * Even with environmentOptions.jsdom.url set, some CI environments (GitHub Actions)\n * still don't provide a fully functional localStorage. This polyfill ensures it works.\n */\nif (\n typeof globalThis.localStorage === 'undefined' ||\n typeof globalThis.localStorage.clear !== 'function'\n) {\n const store = new Map<string, string>()\n const storage: Storage = {\n getItem: (key: string) => store.get(key) ?? null,\n setItem: (key: string, value: string) => {\n store.set(key, String(value))\n },\n removeItem: (key: string) => {\n store.delete(key)\n },\n clear: () => {\n store.clear()\n },\n get length() {\n return store.size\n },\n key: (index: number) => [...store.keys()][index] ?? null,\n }\n Object.defineProperty(globalThis, 'localStorage', {\n value: storage,\n writable: true,\n configurable: true,\n })\n}\n"],"names":[],"mappings":"AAOA,IACE,OAAO,WAAW,iBAAiB,eACnC,OAAO,WAAW,aAAa,UAAU,YACzC;AACA,QAAM,4BAAY,IAAA;AAClB,QAAM,UAAmB;AAAA,IACvB,SAAS,CAAC,QAAgB,MAAM,IAAI,GAAG,KAAK;AAAA,IAC5C,SAAS,CAAC,KAAa,UAAkB;AACvC,YAAM,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,IAC9B;AAAA,IACA,YAAY,CAAC,QAAgB;AAC3B,YAAM,OAAO,GAAG;AAAA,IAClB;AAAA,IACA,OAAO,MAAM;AACX,YAAM,MAAA;AAAA,IACR;AAAA,IACA,IAAI,SAAS;AACX,aAAO,MAAM;AAAA,IACf;AAAA,IACA,KAAK,CAAC,UAAkB,CAAC,GAAG,MAAM,KAAA,CAAM,EAAE,KAAK,KAAK;AAAA,EAAA;AAEtD,SAAO,eAAe,YAAY,gBAAgB;AAAA,IAChD,OAAO;AAAA,IACP,UAAU;AAAA,IACV,cAAc;AAAA,EAAA,CACf;AACH;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function SvgMock(props: React.SVGProps<SVGSVGElement>): import("react/jsx-runtime").JSX.Element;
|
|
File without changes
|