@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.
Files changed (92) hide show
  1. package/README.md +51 -0
  2. package/dist/esm/harness/MockBackendVerifier.d.ts +8 -0
  3. package/dist/esm/harness/MockBackendVerifier.js +15 -0
  4. package/dist/esm/harness/MockBackendVerifier.js.map +1 -0
  5. package/dist/esm/harness/given.d.ts +23 -0
  6. package/dist/esm/harness/given.js +144 -0
  7. package/dist/esm/harness/given.js.map +1 -0
  8. package/dist/esm/index.d.ts +38 -0
  9. package/dist/esm/index.js +45 -0
  10. package/dist/esm/index.js.map +1 -0
  11. package/dist/esm/mock-backend/BrowserStateCfg.d.ts +18 -0
  12. package/dist/esm/mock-backend/BrowserStateCfg.js +33 -0
  13. package/dist/esm/mock-backend/BrowserStateCfg.js.map +1 -0
  14. package/dist/esm/mock-backend/MockBackendConfigurer.d.ts +17 -0
  15. package/dist/esm/mock-backend/MockBackendConfigurer.js +80 -0
  16. package/dist/esm/mock-backend/MockBackendConfigurer.js.map +1 -0
  17. package/dist/esm/mock-backend/MockDb.d.ts +18 -0
  18. package/dist/esm/mock-backend/MockDb.js +56 -0
  19. package/dist/esm/mock-backend/MockDb.js.map +1 -0
  20. package/dist/esm/mock-backend/MockService.d.ts +10 -0
  21. package/dist/esm/mock-backend/MockService.js +16 -0
  22. package/dist/esm/mock-backend/MockService.js.map +1 -0
  23. package/dist/esm/mock-backend/MockUserContext.d.ts +11 -0
  24. package/dist/esm/mock-backend/MockUserContext.js +38 -0
  25. package/dist/esm/mock-backend/MockUserContext.js.map +1 -0
  26. package/dist/esm/mock-backend/magazines.d.ts +22 -0
  27. package/dist/esm/mock-backend/magazines.js +186 -0
  28. package/dist/esm/mock-backend/magazines.js.map +1 -0
  29. package/dist/esm/mock-network/NetworkCatalog.d.ts +12 -0
  30. package/dist/esm/mock-network/NetworkCatalog.js +24 -0
  31. package/dist/esm/mock-network/NetworkCatalog.js.map +1 -0
  32. package/dist/esm/mock-network/NetworkConfigurerCfg.d.ts +13 -0
  33. package/dist/esm/mock-network/NetworkConfigurerCfg.js +16 -0
  34. package/dist/esm/mock-network/NetworkConfigurerCfg.js.map +1 -0
  35. package/dist/esm/mock-network/SharedNetwork.d.ts +13 -0
  36. package/dist/esm/mock-network/SharedNetwork.js +149 -0
  37. package/dist/esm/mock-network/SharedNetwork.js.map +1 -0
  38. package/dist/esm/mock-network/errorFactories.d.ts +11 -0
  39. package/dist/esm/mock-network/errorFactories.js +22 -0
  40. package/dist/esm/mock-network/errorFactories.js.map +1 -0
  41. package/dist/esm/mock-network/makeNetworkReplyWithCatalog.d.ts +3 -0
  42. package/dist/esm/mock-network/makeNetworkReplyWithCatalog.js +17 -0
  43. package/dist/esm/mock-network/makeNetworkReplyWithCatalog.js.map +1 -0
  44. package/dist/esm/setup/polyfillLocalStorage.d.ts +0 -0
  45. package/dist/esm/setup/polyfillLocalStorage.js +25 -0
  46. package/dist/esm/setup/polyfillLocalStorage.js.map +1 -0
  47. package/dist/esm/setup/svgMock.d.ts +1 -0
  48. package/dist/esm/setup/testSetup.d.ts +0 -0
  49. package/dist/esm/setup/testSetup.js +58 -0
  50. package/dist/esm/setup/testSetup.js.map +1 -0
  51. package/dist/esm/tools/AppDetailTools.d.ts +63 -0
  52. package/dist/esm/tools/AppDetailTools.js +180 -0
  53. package/dist/esm/tools/AppDetailTools.js.map +1 -0
  54. package/dist/esm/tools/BrowserState.d.ts +25 -0
  55. package/dist/esm/tools/BrowserState.js +30 -0
  56. package/dist/esm/tools/BrowserState.js.map +1 -0
  57. package/dist/esm/tools/CatalogTools.d.ts +67 -0
  58. package/dist/esm/tools/CatalogTools.js +202 -0
  59. package/dist/esm/tools/CatalogTools.js.map +1 -0
  60. package/dist/esm/tools/ErrorTools.d.ts +15 -0
  61. package/dist/esm/tools/ErrorTools.js +34 -0
  62. package/dist/esm/tools/ErrorTools.js.map +1 -0
  63. package/dist/esm/tools/GalleryTools.d.ts +6 -0
  64. package/dist/esm/tools/GalleryTools.js +22 -0
  65. package/dist/esm/tools/GalleryTools.js.map +1 -0
  66. package/dist/esm/tools/suppressConsole.d.ts +19 -0
  67. package/dist/esm/tools/suppressConsole.js +44 -0
  68. package/dist/esm/tools/suppressConsole.js.map +1 -0
  69. package/package.json +87 -0
  70. package/src/harness/MockBackendVerifier.ts +14 -0
  71. package/src/harness/given.tsx +212 -0
  72. package/src/index.ts +44 -0
  73. package/src/mock-backend/BrowserStateCfg.ts +35 -0
  74. package/src/mock-backend/MockBackendConfigurer.ts +97 -0
  75. package/src/mock-backend/MockDb.ts +66 -0
  76. package/src/mock-backend/MockService.ts +18 -0
  77. package/src/mock-backend/MockUserContext.ts +42 -0
  78. package/src/mock-backend/magazines.ts +245 -0
  79. package/src/mock-network/NetworkCatalog.ts +30 -0
  80. package/src/mock-network/NetworkConfigurerCfg.ts +24 -0
  81. package/src/mock-network/SharedNetwork.ts +100 -0
  82. package/src/mock-network/errorFactories.ts +28 -0
  83. package/src/mock-network/makeNetworkReplyWithCatalog.ts +19 -0
  84. package/src/setup/polyfillLocalStorage.ts +34 -0
  85. package/src/setup/svgMock.tsx +3 -0
  86. package/src/setup/testSetup.ts +58 -0
  87. package/src/tools/AppDetailTools.ts +240 -0
  88. package/src/tools/BrowserState.ts +41 -0
  89. package/src/tools/CatalogTools.ts +254 -0
  90. package/src/tools/ErrorTools.ts +49 -0
  91. package/src/tools/GalleryTools.ts +26 -0
  92. 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,3 @@
1
+ import { MockService } from '../mock-backend/MockService.js';
2
+ import { NetworkCatalog } from './NetworkCatalog.js';
3
+ export declare function makeNetworkReplyWithCatalog(service: MockService): NetworkCatalog;
@@ -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