@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
package/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# @igstack/app-catalog-test-kit
|
|
2
|
+
|
|
3
|
+
Integration test harness for App Catalog. `given()` mounts the real app against
|
|
4
|
+
a mock backend and a mock network (msw), then hands back page objects to drive
|
|
5
|
+
the UI with.
|
|
6
|
+
|
|
7
|
+
Runs in `jsdom` under `vitest` — no browser, no server, no database.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
pnpm add -D @igstack/app-catalog-test-kit jsdom vitest
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Setup
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
// vite.config.ts
|
|
19
|
+
export default defineConfig({
|
|
20
|
+
test: {
|
|
21
|
+
environment: 'jsdom',
|
|
22
|
+
setupFiles: ['@igstack/app-catalog-test-kit/setup'],
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The setup entry installs `@testing-library/jest-dom`, `fake-indexeddb`, a
|
|
28
|
+
`localStorage` polyfill and an SVG module mock.
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { given, magazine } from '@igstack/app-catalog-test-kit'
|
|
34
|
+
|
|
35
|
+
it('filters the catalog by the search query', async () => {
|
|
36
|
+
const { ui } = await given(magazine.full())
|
|
37
|
+
|
|
38
|
+
await ui.catalog.search('taskflow')
|
|
39
|
+
|
|
40
|
+
expect(ui.catalog.getTableData().map((r) => r.name)).toEqual(['TaskFlow'])
|
|
41
|
+
})
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
A _magazine_ is a named fixture: a function that populates the mock backend,
|
|
45
|
+
the mock network and the browser state before the app mounts. `given()` returns
|
|
46
|
+
`{ ui, backend, router }` — page objects, a verifier for what the backend was
|
|
47
|
+
asked, and the live router.
|
|
48
|
+
|
|
49
|
+
## License
|
|
50
|
+
|
|
51
|
+
MIT
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Resource } from '@igstack/app-catalog-backend-core';
|
|
2
|
+
import { MockDb } from '../mock-backend/MockDb.js';
|
|
3
|
+
export declare class MockBackendVerifier {
|
|
4
|
+
readonly db: MockDb;
|
|
5
|
+
constructor(db: MockDb);
|
|
6
|
+
apps(): Resource[];
|
|
7
|
+
getApp(slug: string): Resource;
|
|
8
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class MockBackendVerifier {
|
|
2
|
+
constructor(db) {
|
|
3
|
+
this.db = db;
|
|
4
|
+
}
|
|
5
|
+
apps() {
|
|
6
|
+
return this.db.getResources();
|
|
7
|
+
}
|
|
8
|
+
getApp(slug) {
|
|
9
|
+
return this.db.getResource(slug);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export {
|
|
13
|
+
MockBackendVerifier
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=MockBackendVerifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MockBackendVerifier.js","sources":["../../../src/harness/MockBackendVerifier.ts"],"sourcesContent":["import type { Resource } from '@igstack/app-catalog-backend-core'\nimport type { MockDb } from '../mock-backend/MockDb'\n\nexport class MockBackendVerifier {\n constructor(readonly db: MockDb) {}\n\n apps(): Resource[] {\n return this.db.getResources()\n }\n\n getApp(slug: string): Resource {\n return this.db.getResource(slug)\n }\n}\n"],"names":[],"mappings":"AAGO,MAAM,oBAAoB;AAAA,EAC/B,YAAqB,IAAY;AAAZ,SAAA,KAAA;AAAA,EAAa;AAAA,EAElC,OAAmB;AACjB,WAAO,KAAK,GAAG,aAAA;AAAA,EACjB;AAAA,EAEA,OAAO,MAAwB;AAC7B,WAAO,KAAK,GAAG,YAAY,IAAI;AAAA,EACjC;AACF;"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createAcRouter } from '@igstack/app-catalog-frontend-core/internal';
|
|
2
|
+
import { Magazine } from '../mock-backend/magazines.js';
|
|
3
|
+
import { MockBackendVerifier } from './MockBackendVerifier.js';
|
|
4
|
+
import { GlobalError } from '../tools/ErrorTools.js';
|
|
5
|
+
import { CatalogTools } from '../tools/CatalogTools.js';
|
|
6
|
+
import { AppDetailTools } from '../tools/AppDetailTools.js';
|
|
7
|
+
import { GalleryTools } from '../tools/GalleryTools.js';
|
|
8
|
+
export interface GivenResult {
|
|
9
|
+
ui: UiTools;
|
|
10
|
+
backend: MockBackendVerifier;
|
|
11
|
+
router: ReturnType<typeof createAcRouter>;
|
|
12
|
+
}
|
|
13
|
+
export interface UiTools {
|
|
14
|
+
catalog: CatalogTools;
|
|
15
|
+
app: AppDetailTools;
|
|
16
|
+
gallery: GalleryTools;
|
|
17
|
+
globalError: () => GlobalError;
|
|
18
|
+
}
|
|
19
|
+
export declare function cleanupTestResources(): Promise<void>;
|
|
20
|
+
export declare function given(magazine: Magazine, opts?: {
|
|
21
|
+
initialRoute?: string;
|
|
22
|
+
seedSearch?: string;
|
|
23
|
+
}): Promise<GivenResult>;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { render, waitFor, screen } from "@testing-library/react";
|
|
3
|
+
import { createMemoryHistory } from "@tanstack/react-router";
|
|
4
|
+
import { createTRPCClient, httpLink } from "@trpc/client";
|
|
5
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
6
|
+
import { setupServer } from "msw/node";
|
|
7
|
+
import { SEARCH_STORAGE_KEY, AcDb, createAcRouter, App } from "@igstack/app-catalog-frontend-core/internal";
|
|
8
|
+
import { MockDb } from "../mock-backend/MockDb.js";
|
|
9
|
+
import { MockUserContext } from "../mock-backend/MockUserContext.js";
|
|
10
|
+
import { MockService } from "../mock-backend/MockService.js";
|
|
11
|
+
import { MockBackendConfigurer, resetConfigurerCounter } from "../mock-backend/MockBackendConfigurer.js";
|
|
12
|
+
import { BrowserStateCfg } from "../mock-backend/BrowserStateCfg.js";
|
|
13
|
+
import { NetworkConfigurerCfg } from "../mock-network/NetworkConfigurerCfg.js";
|
|
14
|
+
import { makeNetworkReplyWithCatalog } from "../mock-network/makeNetworkReplyWithCatalog.js";
|
|
15
|
+
import { MockBackendVerifier } from "./MockBackendVerifier.js";
|
|
16
|
+
import { getGlobalError } from "../tools/ErrorTools.js";
|
|
17
|
+
import { browserState } from "../tools/BrowserState.js";
|
|
18
|
+
import { CatalogTools } from "../tools/CatalogTools.js";
|
|
19
|
+
import { AppDetailTools } from "../tools/AppDetailTools.js";
|
|
20
|
+
import { GalleryTools } from "../tools/GalleryTools.js";
|
|
21
|
+
let activeServer = null;
|
|
22
|
+
let activeDb = null;
|
|
23
|
+
async function cleanupTestResources() {
|
|
24
|
+
if (activeServer) {
|
|
25
|
+
activeServer.close();
|
|
26
|
+
activeServer = null;
|
|
27
|
+
}
|
|
28
|
+
if (activeDb) {
|
|
29
|
+
activeDb.close();
|
|
30
|
+
await activeDb.delete();
|
|
31
|
+
activeDb = null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function given(magazine, opts = {}) {
|
|
35
|
+
await cleanupTestResources();
|
|
36
|
+
resetConfigurerCounter();
|
|
37
|
+
const db = new MockDb();
|
|
38
|
+
const userContext = new MockUserContext();
|
|
39
|
+
const backendCfg = new MockBackendConfigurer(db, userContext);
|
|
40
|
+
const browserStateCfg = new BrowserStateCfg();
|
|
41
|
+
const networkCfg = new NetworkConfigurerCfg();
|
|
42
|
+
const ctx = { backendCfg, browserStateCfg, networkCfg };
|
|
43
|
+
magazine(ctx);
|
|
44
|
+
const service = new MockService(db, userContext);
|
|
45
|
+
if (browserStateCfg.shouldDismissOnboarding) {
|
|
46
|
+
browserState.dismissOnboarding();
|
|
47
|
+
}
|
|
48
|
+
if (browserStateCfg.shouldSeedOfflineData) {
|
|
49
|
+
await browserState.seedCatalogCache(service.getAppCatalogData());
|
|
50
|
+
}
|
|
51
|
+
for (const [key, value] of browserStateCfg.localStorageItems) {
|
|
52
|
+
localStorage.setItem(key, value);
|
|
53
|
+
}
|
|
54
|
+
if (opts.seedSearch !== void 0) {
|
|
55
|
+
sessionStorage.setItem(SEARCH_STORAGE_KEY, opts.seedSearch);
|
|
56
|
+
}
|
|
57
|
+
const catalog = makeNetworkReplyWithCatalog(service);
|
|
58
|
+
for (const override of networkCfg.overrides) {
|
|
59
|
+
override(catalog, service);
|
|
60
|
+
}
|
|
61
|
+
const server = setupServer(...catalog.getHandlers());
|
|
62
|
+
server.listen({
|
|
63
|
+
onUnhandledRequest: (req) => {
|
|
64
|
+
console.warn(`[MSW] Unhandled: ${req.method} ${req.url}`);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
activeServer = server;
|
|
68
|
+
const trpcClient = createTRPCClient({
|
|
69
|
+
links: [
|
|
70
|
+
httpLink({
|
|
71
|
+
url: "http://localhost/api/trpc"
|
|
72
|
+
})
|
|
73
|
+
]
|
|
74
|
+
});
|
|
75
|
+
const acDb = new AcDb();
|
|
76
|
+
activeDb = acDb;
|
|
77
|
+
const queryClient = new QueryClient({
|
|
78
|
+
defaultOptions: {
|
|
79
|
+
queries: {
|
|
80
|
+
retry: false,
|
|
81
|
+
gcTime: 0,
|
|
82
|
+
meta: { db: acDb, trpcClient }
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
const router = createAcRouter({
|
|
87
|
+
history: createMemoryHistory({
|
|
88
|
+
initialEntries: [opts.initialRoute ?? "/catalog/apps"]
|
|
89
|
+
}),
|
|
90
|
+
context: {
|
|
91
|
+
queryClient,
|
|
92
|
+
trpcClient,
|
|
93
|
+
db: acDb,
|
|
94
|
+
plugins: [],
|
|
95
|
+
boostrapHealth: {}
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
const sessionResponse = service.getSessionResponse();
|
|
99
|
+
if (sessionResponse && typeof sessionResponse === "object" && "user" in sessionResponse) {
|
|
100
|
+
localStorage.setItem(
|
|
101
|
+
"ac_auth_user",
|
|
102
|
+
JSON.stringify(sessionResponse.user)
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
render(
|
|
106
|
+
/* @__PURE__ */ jsx(
|
|
107
|
+
App,
|
|
108
|
+
{
|
|
109
|
+
router,
|
|
110
|
+
queryClient,
|
|
111
|
+
trpcClient,
|
|
112
|
+
db: acDb
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
);
|
|
116
|
+
await waitFor(
|
|
117
|
+
() => {
|
|
118
|
+
const hasAlert = screen.queryByRole("alert");
|
|
119
|
+
const hasTable = screen.queryAllByRole("table").length > 0;
|
|
120
|
+
const hasSearchbox = screen.queryByLabelText("Search apps");
|
|
121
|
+
const hasTanstackError = document.body.textContent.includes(
|
|
122
|
+
"Something went wrong"
|
|
123
|
+
);
|
|
124
|
+
const hasNotFound = document.body.textContent.includes("404 Not Found");
|
|
125
|
+
if (!hasAlert && !hasTable && !hasSearchbox && !hasTanstackError && !hasNotFound) {
|
|
126
|
+
throw new Error("Still loading...");
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
{ timeout: 1e4 }
|
|
130
|
+
);
|
|
131
|
+
const backend = new MockBackendVerifier(db);
|
|
132
|
+
const ui = {
|
|
133
|
+
catalog: new CatalogTools(),
|
|
134
|
+
app: new AppDetailTools(),
|
|
135
|
+
gallery: new GalleryTools(),
|
|
136
|
+
globalError: () => getGlobalError()
|
|
137
|
+
};
|
|
138
|
+
return { ui, backend, router };
|
|
139
|
+
}
|
|
140
|
+
export {
|
|
141
|
+
cleanupTestResources,
|
|
142
|
+
given
|
|
143
|
+
};
|
|
144
|
+
//# sourceMappingURL=given.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"given.js","sources":["../../../src/harness/given.tsx"],"sourcesContent":["import { render, screen, waitFor } from '@testing-library/react'\nimport { createMemoryHistory } from '@tanstack/react-router'\nimport { createTRPCClient, httpLink } from '@trpc/client'\nimport { QueryClient } from '@tanstack/react-query'\nimport type { TRPCRouter } from '@igstack/app-catalog-backend-core'\nimport { setupServer } from 'msw/node'\nimport type { SetupServer } from 'msw/node'\n\nimport {\n AcDb,\n App,\n SEARCH_STORAGE_KEY,\n createAcRouter,\n} from '@igstack/app-catalog-frontend-core/internal'\n\nimport { MockDb } from '../mock-backend/MockDb'\nimport { MockUserContext } from '../mock-backend/MockUserContext'\nimport { MockService } from '../mock-backend/MockService'\nimport {\n MockBackendConfigurer,\n resetConfigurerCounter,\n} from '../mock-backend/MockBackendConfigurer'\nimport { BrowserStateCfg } from '../mock-backend/BrowserStateCfg'\nimport { NetworkConfigurerCfg } from '../mock-network/NetworkConfigurerCfg'\nimport type { ConfigurerContext, Magazine } from '../mock-backend/magazines'\nimport { makeNetworkReplyWithCatalog } from '../mock-network/makeNetworkReplyWithCatalog'\nimport { MockBackendVerifier } from './MockBackendVerifier'\nimport { getGlobalError } from '../tools/ErrorTools'\nimport type { GlobalError } from '../tools/ErrorTools'\nimport { browserState } from '../tools/BrowserState'\nimport { CatalogTools } from '../tools/CatalogTools'\nimport { AppDetailTools } from '../tools/AppDetailTools'\nimport { GalleryTools } from '../tools/GalleryTools'\n\nexport interface GivenResult {\n ui: UiTools\n backend: MockBackendVerifier\n router: ReturnType<typeof createAcRouter>\n}\n\nexport interface UiTools {\n catalog: CatalogTools\n app: AppDetailTools\n gallery: GalleryTools\n globalError: () => GlobalError\n}\n\n// Track active resources for cleanup\nlet activeServer: SetupServer | null = null\nlet activeDb: AcDb | null = null\n\nexport async function cleanupTestResources(): Promise<void> {\n if (activeServer) {\n activeServer.close()\n activeServer = null\n }\n if (activeDb) {\n activeDb.close()\n await activeDb.delete()\n activeDb = null\n }\n}\n\nexport async function given(\n magazine: Magazine,\n opts: { initialRoute?: string; seedSearch?: string } = {},\n): Promise<GivenResult> {\n // Clean up any previous resources\n await cleanupTestResources()\n resetConfigurerCounter()\n\n // 1. Create cfg objects (data carriers)\n const db = new MockDb()\n const userContext = new MockUserContext()\n const backendCfg = new MockBackendConfigurer(db, userContext)\n const browserStateCfg = new BrowserStateCfg()\n const networkCfg = new NetworkConfigurerCfg()\n\n const ctx: ConfigurerContext = { backendCfg, browserStateCfg, networkCfg }\n\n // 2. Run magazine configurer — populates all three cfg objects\n magazine(ctx)\n\n // 3. Materialize backend service\n const service = new MockService(db, userContext)\n\n // 4. Apply browser state using data from backend service\n if (browserStateCfg.shouldDismissOnboarding) {\n browserState.dismissOnboarding()\n }\n if (browserStateCfg.shouldSeedOfflineData) {\n await browserState.seedCatalogCache(service.getAppCatalogData())\n }\n for (const [key, value] of browserStateCfg.localStorageItems) {\n localStorage.setItem(key, value)\n }\n\n // Seed the persisted catalog search (#27): simulates a returning user who had\n // typed a query before navigating. Backs the same sessionStorage key the\n // provider reads, so the search input repopulates without any `?q=` in the URL.\n if (opts.seedSearch !== undefined) {\n sessionStorage.setItem(SEARCH_STORAGE_KEY, opts.seedSearch)\n }\n\n // 5. Build network catalog from backend service, then apply overrides\n const catalog = makeNetworkReplyWithCatalog(service)\n for (const override of networkCfg.overrides) {\n override(catalog, service)\n }\n\n // 6. Set up MSW server\n const server = setupServer(...catalog.getHandlers())\n server.listen({\n onUnhandledRequest: (req) => {\n console.warn(`[MSW] Unhandled: ${req.method} ${req.url}`)\n },\n })\n activeServer = server\n\n // 7. Create real app infrastructure\n const trpcClient = createTRPCClient<TRPCRouter>({\n links: [\n httpLink({\n url: 'http://localhost/api/trpc',\n }),\n ],\n })\n\n const acDb = new AcDb()\n activeDb = acDb\n const queryClient = new QueryClient({\n defaultOptions: {\n queries: {\n retry: false,\n gcTime: 0,\n meta: { db: acDb, trpcClient },\n },\n },\n })\n\n const router = createAcRouter({\n history: createMemoryHistory({\n initialEntries: [opts.initialRoute ?? '/catalog/apps'],\n }),\n context: {\n queryClient,\n trpcClient,\n db: acDb,\n plugins: [],\n boostrapHealth: {},\n },\n })\n\n // 7b. Seed auth user in localStorage for optimistic rendering\n // (mirrors real-app behavior where localStorage caches the user after first login)\n const sessionResponse = service.getSessionResponse()\n if (\n sessionResponse &&\n typeof sessionResponse === 'object' &&\n 'user' in sessionResponse\n ) {\n localStorage.setItem(\n 'ac_auth_user',\n JSON.stringify((sessionResponse as { user: unknown }).user),\n )\n }\n\n // 8. Render\n render(\n <App\n router={router}\n queryClient={queryClient}\n trpcClient={trpcClient}\n db={acDb}\n />,\n )\n\n // 9. Wait for initial load (either data loaded or error shown)\n await waitFor(\n () => {\n const hasAlert = screen.queryByRole('alert')\n const hasTable = screen.queryAllByRole('table').length > 0\n const hasSearchbox = screen.queryByLabelText('Search apps')\n const hasTanstackError = document.body.textContent.includes(\n 'Something went wrong',\n )\n const hasNotFound = document.body.textContent.includes('404 Not Found')\n if (\n !hasAlert &&\n !hasTable &&\n !hasSearchbox &&\n !hasTanstackError &&\n !hasNotFound\n ) {\n throw new Error('Still loading...')\n }\n },\n { timeout: 10000 },\n )\n\n // 10. Return tools\n const backend = new MockBackendVerifier(db)\n\n const ui: UiTools = {\n catalog: new CatalogTools(),\n app: new AppDetailTools(),\n gallery: new GalleryTools(),\n globalError: () => getGlobalError(),\n }\n\n return { ui, backend, router }\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AAgDA,IAAI,eAAmC;AACvC,IAAI,WAAwB;AAE5B,eAAsB,uBAAsC;AAC1D,MAAI,cAAc;AAChB,iBAAa,MAAA;AACb,mBAAe;AAAA,EACjB;AACA,MAAI,UAAU;AACZ,aAAS,MAAA;AACT,UAAM,SAAS,OAAA;AACf,eAAW;AAAA,EACb;AACF;AAEA,eAAsB,MACpB,UACA,OAAuD,IACjC;AAEtB,QAAM,qBAAA;AACN,yBAAA;AAGA,QAAM,KAAK,IAAI,OAAA;AACf,QAAM,cAAc,IAAI,gBAAA;AACxB,QAAM,aAAa,IAAI,sBAAsB,IAAI,WAAW;AAC5D,QAAM,kBAAkB,IAAI,gBAAA;AAC5B,QAAM,aAAa,IAAI,qBAAA;AAEvB,QAAM,MAAyB,EAAE,YAAY,iBAAiB,WAAA;AAG9D,WAAS,GAAG;AAGZ,QAAM,UAAU,IAAI,YAAY,IAAI,WAAW;AAG/C,MAAI,gBAAgB,yBAAyB;AAC3C,iBAAa,kBAAA;AAAA,EACf;AACA,MAAI,gBAAgB,uBAAuB;AACzC,UAAM,aAAa,iBAAiB,QAAQ,kBAAA,CAAmB;AAAA,EACjE;AACA,aAAW,CAAC,KAAK,KAAK,KAAK,gBAAgB,mBAAmB;AAC5D,iBAAa,QAAQ,KAAK,KAAK;AAAA,EACjC;AAKA,MAAI,KAAK,eAAe,QAAW;AACjC,mBAAe,QAAQ,oBAAoB,KAAK,UAAU;AAAA,EAC5D;AAGA,QAAM,UAAU,4BAA4B,OAAO;AACnD,aAAW,YAAY,WAAW,WAAW;AAC3C,aAAS,SAAS,OAAO;AAAA,EAC3B;AAGA,QAAM,SAAS,YAAY,GAAG,QAAQ,aAAa;AACnD,SAAO,OAAO;AAAA,IACZ,oBAAoB,CAAC,QAAQ;AAC3B,cAAQ,KAAK,oBAAoB,IAAI,MAAM,IAAI,IAAI,GAAG,EAAE;AAAA,IAC1D;AAAA,EAAA,CACD;AACD,iBAAe;AAGf,QAAM,aAAa,iBAA6B;AAAA,IAC9C,OAAO;AAAA,MACL,SAAS;AAAA,QACP,KAAK;AAAA,MAAA,CACN;AAAA,IAAA;AAAA,EACH,CACD;AAED,QAAM,OAAO,IAAI,KAAA;AACjB,aAAW;AACX,QAAM,cAAc,IAAI,YAAY;AAAA,IAClC,gBAAgB;AAAA,MACd,SAAS;AAAA,QACP,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,MAAM,EAAE,IAAI,MAAM,WAAA;AAAA,MAAW;AAAA,IAC/B;AAAA,EACF,CACD;AAED,QAAM,SAAS,eAAe;AAAA,IAC5B,SAAS,oBAAoB;AAAA,MAC3B,gBAAgB,CAAC,KAAK,gBAAgB,eAAe;AAAA,IAAA,CACtD;AAAA,IACD,SAAS;AAAA,MACP;AAAA,MACA;AAAA,MACA,IAAI;AAAA,MACJ,SAAS,CAAA;AAAA,MACT,gBAAgB,CAAA;AAAA,IAAC;AAAA,EACnB,CACD;AAID,QAAM,kBAAkB,QAAQ,mBAAA;AAChC,MACE,mBACA,OAAO,oBAAoB,YAC3B,UAAU,iBACV;AACA,iBAAa;AAAA,MACX;AAAA,MACA,KAAK,UAAW,gBAAsC,IAAI;AAAA,IAAA;AAAA,EAE9D;AAGA;AAAA,IACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA,IAAI;AAAA,MAAA;AAAA,IAAA;AAAA,EACN;AAIF,QAAM;AAAA,IACJ,MAAM;AACJ,YAAM,WAAW,OAAO,YAAY,OAAO;AAC3C,YAAM,WAAW,OAAO,eAAe,OAAO,EAAE,SAAS;AACzD,YAAM,eAAe,OAAO,iBAAiB,aAAa;AAC1D,YAAM,mBAAmB,SAAS,KAAK,YAAY;AAAA,QACjD;AAAA,MAAA;AAEF,YAAM,cAAc,SAAS,KAAK,YAAY,SAAS,eAAe;AACtE,UACE,CAAC,YACD,CAAC,YACD,CAAC,gBACD,CAAC,oBACD,CAAC,aACD;AACA,cAAM,IAAI,MAAM,kBAAkB;AAAA,MACpC;AAAA,IACF;AAAA,IACA,EAAE,SAAS,IAAA;AAAA,EAAM;AAInB,QAAM,UAAU,IAAI,oBAAoB,EAAE;AAE1C,QAAM,KAAc;AAAA,IAClB,SAAS,IAAI,aAAA;AAAA,IACb,KAAK,IAAI,eAAA;AAAA,IACT,SAAS,IAAI,aAAA;AAAA,IACb,aAAa,MAAM,eAAA;AAAA,EAAe;AAGpC,SAAO,EAAE,IAAI,SAAS,OAAA;AACxB;"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration test harness for App Catalog.
|
|
3
|
+
*
|
|
4
|
+
* `given(...)` mounts the real `App` against a mock backend and mock network,
|
|
5
|
+
* and hands back page objects (`ui.catalog`, `ui.app`, ...) to drive it with.
|
|
6
|
+
* Consumers supply `vitest`, `jsdom` and the setup file:
|
|
7
|
+
*
|
|
8
|
+
* // vite.config.ts
|
|
9
|
+
* test: {
|
|
10
|
+
* environment: 'jsdom',
|
|
11
|
+
* setupFiles: ['@igstack/app-catalog-test-kit/setup'],
|
|
12
|
+
* }
|
|
13
|
+
*/
|
|
14
|
+
export { given, cleanupTestResources } from './harness/given.js';
|
|
15
|
+
export type { GivenResult, UiTools } from './harness/given.js';
|
|
16
|
+
export { MockBackendVerifier } from './harness/MockBackendVerifier.js';
|
|
17
|
+
export { magazine } from './mock-backend/magazines.js';
|
|
18
|
+
export type { ConfigurerContext, Magazine } from './mock-backend/magazines.js';
|
|
19
|
+
export { MockBackendConfigurer, resetConfigurerCounter, } from './mock-backend/MockBackendConfigurer.js';
|
|
20
|
+
export { MockDb } from './mock-backend/MockDb.js';
|
|
21
|
+
export { MockService } from './mock-backend/MockService.js';
|
|
22
|
+
export { MockUserContext } from './mock-backend/MockUserContext.js';
|
|
23
|
+
export type { UserConfig } from './mock-backend/MockUserContext.js';
|
|
24
|
+
export { BrowserStateCfg } from './mock-backend/BrowserStateCfg.js';
|
|
25
|
+
export { NetworkConfigurerCfg } from './mock-network/NetworkConfigurerCfg.js';
|
|
26
|
+
export type { NetworkOverrideFn } from './mock-network/NetworkConfigurerCfg.js';
|
|
27
|
+
export { NetworkCatalog } from './mock-network/NetworkCatalog.js';
|
|
28
|
+
export type { NetworkInterceptor } from './mock-network/NetworkCatalog.js';
|
|
29
|
+
export { SharedNetwork } from './mock-network/SharedNetwork.js';
|
|
30
|
+
export { makeNetworkReplyWithCatalog } from './mock-network/makeNetworkReplyWithCatalog.js';
|
|
31
|
+
export * from './mock-network/errorFactories.js';
|
|
32
|
+
export { CatalogTools } from './tools/CatalogTools.js';
|
|
33
|
+
export { AppDetailTools } from './tools/AppDetailTools.js';
|
|
34
|
+
export { GalleryTools } from './tools/GalleryTools.js';
|
|
35
|
+
export { getGlobalError } from './tools/ErrorTools.js';
|
|
36
|
+
export type { GlobalError } from './tools/ErrorTools.js';
|
|
37
|
+
export { browserState } from './tools/BrowserState.js';
|
|
38
|
+
export { suppressConsole, suppressConsoleGlobal } from './tools/suppressConsole.js';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { cleanupTestResources, given } from "./harness/given.js";
|
|
2
|
+
import { MockBackendVerifier } from "./harness/MockBackendVerifier.js";
|
|
3
|
+
import { magazine } from "./mock-backend/magazines.js";
|
|
4
|
+
import { MockBackendConfigurer, resetConfigurerCounter } from "./mock-backend/MockBackendConfigurer.js";
|
|
5
|
+
import { MockDb } from "./mock-backend/MockDb.js";
|
|
6
|
+
import { MockService } from "./mock-backend/MockService.js";
|
|
7
|
+
import { MockUserContext } from "./mock-backend/MockUserContext.js";
|
|
8
|
+
import { BrowserStateCfg } from "./mock-backend/BrowserStateCfg.js";
|
|
9
|
+
import { NetworkConfigurerCfg } from "./mock-network/NetworkConfigurerCfg.js";
|
|
10
|
+
import { NetworkCatalog } from "./mock-network/NetworkCatalog.js";
|
|
11
|
+
import { SharedNetwork } from "./mock-network/SharedNetwork.js";
|
|
12
|
+
import { makeNetworkReplyWithCatalog } from "./mock-network/makeNetworkReplyWithCatalog.js";
|
|
13
|
+
import { makeConnectionResetError, makeHtmlResponseError } from "./mock-network/errorFactories.js";
|
|
14
|
+
import { CatalogTools } from "./tools/CatalogTools.js";
|
|
15
|
+
import { AppDetailTools } from "./tools/AppDetailTools.js";
|
|
16
|
+
import { GalleryTools } from "./tools/GalleryTools.js";
|
|
17
|
+
import { getGlobalError } from "./tools/ErrorTools.js";
|
|
18
|
+
import { browserState } from "./tools/BrowserState.js";
|
|
19
|
+
import { suppressConsole, suppressConsoleGlobal } from "./tools/suppressConsole.js";
|
|
20
|
+
export {
|
|
21
|
+
AppDetailTools,
|
|
22
|
+
BrowserStateCfg,
|
|
23
|
+
CatalogTools,
|
|
24
|
+
GalleryTools,
|
|
25
|
+
MockBackendConfigurer,
|
|
26
|
+
MockBackendVerifier,
|
|
27
|
+
MockDb,
|
|
28
|
+
MockService,
|
|
29
|
+
MockUserContext,
|
|
30
|
+
NetworkCatalog,
|
|
31
|
+
NetworkConfigurerCfg,
|
|
32
|
+
SharedNetwork,
|
|
33
|
+
browserState,
|
|
34
|
+
cleanupTestResources,
|
|
35
|
+
getGlobalError,
|
|
36
|
+
given,
|
|
37
|
+
magazine,
|
|
38
|
+
makeConnectionResetError,
|
|
39
|
+
makeHtmlResponseError,
|
|
40
|
+
makeNetworkReplyWithCatalog,
|
|
41
|
+
resetConfigurerCounter,
|
|
42
|
+
suppressConsole,
|
|
43
|
+
suppressConsoleGlobal
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data-carrier that collects browser state configuration.
|
|
3
|
+
* Does not execute anything — `given()` materializes it after backend is ready.
|
|
4
|
+
*/
|
|
5
|
+
export declare class BrowserStateCfg {
|
|
6
|
+
private _offlineData;
|
|
7
|
+
private _onboardingDismissed;
|
|
8
|
+
private _localStorage;
|
|
9
|
+
/** Seed IndexedDB with current backend data (simulates returning user) */
|
|
10
|
+
withOfflineData(): void;
|
|
11
|
+
/** Mark onboarding as dismissed */
|
|
12
|
+
dismissOnboarding(): void;
|
|
13
|
+
/** Set an arbitrary localStorage item */
|
|
14
|
+
withLocalStorageItem(key: string, value: string): void;
|
|
15
|
+
get shouldSeedOfflineData(): boolean;
|
|
16
|
+
get shouldDismissOnboarding(): boolean;
|
|
17
|
+
get localStorageItems(): Map<string, string>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
class BrowserStateCfg {
|
|
2
|
+
constructor() {
|
|
3
|
+
this._offlineData = false;
|
|
4
|
+
this._onboardingDismissed = false;
|
|
5
|
+
this._localStorage = /* @__PURE__ */ new Map();
|
|
6
|
+
}
|
|
7
|
+
/** Seed IndexedDB with current backend data (simulates returning user) */
|
|
8
|
+
withOfflineData() {
|
|
9
|
+
this._offlineData = true;
|
|
10
|
+
}
|
|
11
|
+
/** Mark onboarding as dismissed */
|
|
12
|
+
dismissOnboarding() {
|
|
13
|
+
this._onboardingDismissed = true;
|
|
14
|
+
}
|
|
15
|
+
/** Set an arbitrary localStorage item */
|
|
16
|
+
withLocalStorageItem(key, value) {
|
|
17
|
+
this._localStorage.set(key, value);
|
|
18
|
+
}
|
|
19
|
+
// Read-only accessors for given() to materialize
|
|
20
|
+
get shouldSeedOfflineData() {
|
|
21
|
+
return this._offlineData;
|
|
22
|
+
}
|
|
23
|
+
get shouldDismissOnboarding() {
|
|
24
|
+
return this._onboardingDismissed;
|
|
25
|
+
}
|
|
26
|
+
get localStorageItems() {
|
|
27
|
+
return this._localStorage;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
BrowserStateCfg
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=BrowserStateCfg.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BrowserStateCfg.js","sources":["../../../src/mock-backend/BrowserStateCfg.ts"],"sourcesContent":["/**\n * Data-carrier that collects browser state configuration.\n * Does not execute anything — `given()` materializes it after backend is ready.\n */\nexport class BrowserStateCfg {\n private _offlineData = false\n private _onboardingDismissed = false\n private _localStorage = new Map<string, string>()\n\n /** Seed IndexedDB with current backend data (simulates returning user) */\n withOfflineData(): void {\n this._offlineData = true\n }\n\n /** Mark onboarding as dismissed */\n dismissOnboarding(): void {\n this._onboardingDismissed = true\n }\n\n /** Set an arbitrary localStorage item */\n withLocalStorageItem(key: string, value: string): void {\n this._localStorage.set(key, value)\n }\n\n // Read-only accessors for given() to materialize\n get shouldSeedOfflineData(): boolean {\n return this._offlineData\n }\n get shouldDismissOnboarding(): boolean {\n return this._onboardingDismissed\n }\n get localStorageItems(): Map<string, string> {\n return this._localStorage\n }\n}\n"],"names":[],"mappings":"AAIO,MAAM,gBAAgB;AAAA,EAAtB,cAAA;AACL,SAAQ,eAAe;AACvB,SAAQ,uBAAuB;AAC/B,SAAQ,oCAAoB,IAAA;AAAA,EAAoB;AAAA;AAAA,EAGhD,kBAAwB;AACtB,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA,EAGA,oBAA0B;AACxB,SAAK,uBAAuB;AAAA,EAC9B;AAAA;AAAA,EAGA,qBAAqB,KAAa,OAAqB;AACrD,SAAK,cAAc,IAAI,KAAK,KAAK;AAAA,EACnC;AAAA;AAAA,EAGA,IAAI,wBAAiC;AACnC,WAAO,KAAK;AAAA,EACd;AAAA,EACA,IAAI,0BAAmC;AACrC,WAAO,KAAK;AAAA,EACd;AAAA,EACA,IAAI,oBAAyC;AAC3C,WAAO,KAAK;AAAA,EACd;AACF;"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { AppApprovalMethod, GroupingTagDefinition, Resource } from '@igstack/app-catalog-backend-core';
|
|
2
|
+
import { MockDb } from './MockDb.js';
|
|
3
|
+
import { MockUserContext, UserConfig } from './MockUserContext.js';
|
|
4
|
+
export declare class MockBackendConfigurer {
|
|
5
|
+
readonly db: MockDb;
|
|
6
|
+
readonly userContext: MockUserContext;
|
|
7
|
+
constructor(db: MockDb, userContext: MockUserContext);
|
|
8
|
+
withApp(overrides?: Partial<Resource>): Resource;
|
|
9
|
+
withTag(overrides?: Partial<GroupingTagDefinition>): GroupingTagDefinition;
|
|
10
|
+
withApprovalMethod(overrides?: Partial<AppApprovalMethod>): AppApprovalMethod;
|
|
11
|
+
withSubResource(overrides: Partial<Resource> & {
|
|
12
|
+
appSlug: string;
|
|
13
|
+
}): Resource;
|
|
14
|
+
withUser(overrides: Partial<UserConfig>): void;
|
|
15
|
+
}
|
|
16
|
+
/** Reset the counter between tests */
|
|
17
|
+
export declare function resetConfigurerCounter(): void;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
let counter = 0;
|
|
2
|
+
function nextId() {
|
|
3
|
+
counter++;
|
|
4
|
+
return `test-id-${counter}`;
|
|
5
|
+
}
|
|
6
|
+
function nextSlug() {
|
|
7
|
+
return `app-${counter}`;
|
|
8
|
+
}
|
|
9
|
+
class MockBackendConfigurer {
|
|
10
|
+
constructor(db, userContext) {
|
|
11
|
+
this.db = db;
|
|
12
|
+
this.userContext = userContext;
|
|
13
|
+
}
|
|
14
|
+
withApp(overrides) {
|
|
15
|
+
const id = (overrides == null ? void 0 : overrides.id) ?? nextId();
|
|
16
|
+
const app = {
|
|
17
|
+
id,
|
|
18
|
+
slug: (overrides == null ? void 0 : overrides.slug) ?? nextSlug(),
|
|
19
|
+
displayName: (overrides == null ? void 0 : overrides.displayName) ?? `App ${counter}`,
|
|
20
|
+
description: overrides == null ? void 0 : overrides.description,
|
|
21
|
+
tags: (overrides == null ? void 0 : overrides.tags) ?? [],
|
|
22
|
+
screenshotIds: (overrides == null ? void 0 : overrides.screenshotIds) ?? [],
|
|
23
|
+
...overrides
|
|
24
|
+
};
|
|
25
|
+
this.db.upsertResource(app);
|
|
26
|
+
return app;
|
|
27
|
+
}
|
|
28
|
+
withTag(overrides) {
|
|
29
|
+
const def = {
|
|
30
|
+
prefix: (overrides == null ? void 0 : overrides.prefix) ?? `tag-${nextId()}`,
|
|
31
|
+
displayName: (overrides == null ? void 0 : overrides.displayName) ?? "Tag",
|
|
32
|
+
description: (overrides == null ? void 0 : overrides.description) ?? "",
|
|
33
|
+
values: (overrides == null ? void 0 : overrides.values) ?? [],
|
|
34
|
+
...overrides
|
|
35
|
+
};
|
|
36
|
+
this.db.setTagDefinitions([...this.db.tagsDefinitions, def]);
|
|
37
|
+
return def;
|
|
38
|
+
}
|
|
39
|
+
withApprovalMethod(overrides) {
|
|
40
|
+
const slug = (overrides == null ? void 0 : overrides.slug) ?? `method-${nextId()}`;
|
|
41
|
+
const method = {
|
|
42
|
+
slug,
|
|
43
|
+
type: "service",
|
|
44
|
+
displayName: (overrides == null ? void 0 : overrides.displayName) ?? "Approval Method",
|
|
45
|
+
config: {},
|
|
46
|
+
...overrides
|
|
47
|
+
};
|
|
48
|
+
this.db.setApprovalMethods([...this.db.approvalMethods, method]);
|
|
49
|
+
return method;
|
|
50
|
+
}
|
|
51
|
+
withSubResource(overrides) {
|
|
52
|
+
const appSlug = overrides.appSlug;
|
|
53
|
+
const id = overrides.id ?? nextId();
|
|
54
|
+
const resource = {
|
|
55
|
+
id,
|
|
56
|
+
slug: overrides.slug ?? `sr-${id}`,
|
|
57
|
+
displayName: overrides.displayName ?? `Sub Resource ${counter}`,
|
|
58
|
+
aliases: overrides.aliases ?? [],
|
|
59
|
+
approverSlugs: overrides.approverSlugs ?? [],
|
|
60
|
+
parentSlug: appSlug,
|
|
61
|
+
tier: overrides.tier,
|
|
62
|
+
ownerPersonSlug: overrides.ownerPersonSlug,
|
|
63
|
+
accessRequest: overrides.accessRequest,
|
|
64
|
+
description: overrides.description
|
|
65
|
+
};
|
|
66
|
+
this.db.upsertResource(resource);
|
|
67
|
+
return resource;
|
|
68
|
+
}
|
|
69
|
+
withUser(overrides) {
|
|
70
|
+
this.userContext.setUser(overrides);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function resetConfigurerCounter() {
|
|
74
|
+
counter = 0;
|
|
75
|
+
}
|
|
76
|
+
export {
|
|
77
|
+
MockBackendConfigurer,
|
|
78
|
+
resetConfigurerCounter
|
|
79
|
+
};
|
|
80
|
+
//# sourceMappingURL=MockBackendConfigurer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MockBackendConfigurer.js","sources":["../../../src/mock-backend/MockBackendConfigurer.ts"],"sourcesContent":["import type {\n AppApprovalMethod,\n GroupingTagDefinition,\n Resource,\n} from '@igstack/app-catalog-backend-core'\nimport type { MockDb } from './MockDb'\nimport type { MockUserContext, UserConfig } from './MockUserContext'\n\nlet counter = 0\n\nfunction nextId(): string {\n counter++\n return `test-id-${counter}`\n}\n\nfunction nextSlug(): string {\n return `app-${counter}`\n}\n\nexport class MockBackendConfigurer {\n constructor(\n readonly db: MockDb,\n readonly userContext: MockUserContext,\n ) {}\n\n withApp(overrides?: Partial<Resource>): Resource {\n const id = overrides?.id ?? nextId()\n const app: Resource = {\n id,\n slug: overrides?.slug ?? nextSlug(),\n displayName: overrides?.displayName ?? `App ${counter}`,\n description: overrides?.description,\n tags: overrides?.tags ?? [],\n screenshotIds: overrides?.screenshotIds ?? [],\n ...overrides,\n }\n this.db.upsertResource(app)\n return app\n }\n\n withTag(overrides?: Partial<GroupingTagDefinition>): GroupingTagDefinition {\n const def: GroupingTagDefinition = {\n prefix: overrides?.prefix ?? `tag-${nextId()}`,\n displayName: overrides?.displayName ?? 'Tag',\n description: overrides?.description ?? '',\n values: overrides?.values ?? [],\n ...overrides,\n }\n this.db.setTagDefinitions([...this.db.tagsDefinitions, def])\n return def\n }\n\n withApprovalMethod(\n overrides?: Partial<AppApprovalMethod>,\n ): AppApprovalMethod {\n const slug = overrides?.slug ?? `method-${nextId()}`\n const method = {\n slug,\n type: 'service' as const,\n displayName: overrides?.displayName ?? 'Approval Method',\n config: {},\n ...overrides,\n } as AppApprovalMethod\n this.db.setApprovalMethods([...this.db.approvalMethods, method])\n return method\n }\n\n withSubResource(\n overrides: Partial<Resource> & { appSlug: string },\n ): Resource {\n const appSlug = overrides.appSlug\n const id = overrides.id ?? nextId()\n const resource: Resource = {\n id,\n slug: overrides.slug ?? `sr-${id}`,\n displayName: overrides.displayName ?? `Sub Resource ${counter}`,\n aliases: overrides.aliases ?? [],\n approverSlugs: overrides.approverSlugs ?? [],\n parentSlug: appSlug,\n tier: overrides.tier,\n ownerPersonSlug: overrides.ownerPersonSlug,\n accessRequest: overrides.accessRequest,\n description: overrides.description,\n }\n this.db.upsertResource(resource)\n return resource\n }\n\n withUser(overrides: Partial<UserConfig>): void {\n this.userContext.setUser(overrides)\n }\n}\n\n/** Reset the counter between tests */\nexport function resetConfigurerCounter(): void {\n counter = 0\n}\n"],"names":[],"mappings":"AAQA,IAAI,UAAU;AAEd,SAAS,SAAiB;AACxB;AACA,SAAO,WAAW,OAAO;AAC3B;AAEA,SAAS,WAAmB;AAC1B,SAAO,OAAO,OAAO;AACvB;AAEO,MAAM,sBAAsB;AAAA,EACjC,YACW,IACA,aACT;AAFS,SAAA,KAAA;AACA,SAAA,cAAA;AAAA,EACR;AAAA,EAEH,QAAQ,WAAyC;AAC/C,UAAM,MAAK,uCAAW,OAAM,OAAA;AAC5B,UAAM,MAAgB;AAAA,MACpB;AAAA,MACA,OAAM,uCAAW,SAAQ,SAAA;AAAA,MACzB,cAAa,uCAAW,gBAAe,OAAO,OAAO;AAAA,MACrD,aAAa,uCAAW;AAAA,MACxB,OAAM,uCAAW,SAAQ,CAAA;AAAA,MACzB,gBAAe,uCAAW,kBAAiB,CAAA;AAAA,MAC3C,GAAG;AAAA,IAAA;AAEL,SAAK,GAAG,eAAe,GAAG;AAC1B,WAAO;AAAA,EACT;AAAA,EAEA,QAAQ,WAAmE;AACzE,UAAM,MAA6B;AAAA,MACjC,SAAQ,uCAAW,WAAU,OAAO,QAAQ;AAAA,MAC5C,cAAa,uCAAW,gBAAe;AAAA,MACvC,cAAa,uCAAW,gBAAe;AAAA,MACvC,SAAQ,uCAAW,WAAU,CAAA;AAAA,MAC7B,GAAG;AAAA,IAAA;AAEL,SAAK,GAAG,kBAAkB,CAAC,GAAG,KAAK,GAAG,iBAAiB,GAAG,CAAC;AAC3D,WAAO;AAAA,EACT;AAAA,EAEA,mBACE,WACmB;AACnB,UAAM,QAAO,uCAAW,SAAQ,UAAU,QAAQ;AAClD,UAAM,SAAS;AAAA,MACb;AAAA,MACA,MAAM;AAAA,MACN,cAAa,uCAAW,gBAAe;AAAA,MACvC,QAAQ,CAAA;AAAA,MACR,GAAG;AAAA,IAAA;AAEL,SAAK,GAAG,mBAAmB,CAAC,GAAG,KAAK,GAAG,iBAAiB,MAAM,CAAC;AAC/D,WAAO;AAAA,EACT;AAAA,EAEA,gBACE,WACU;AACV,UAAM,UAAU,UAAU;AAC1B,UAAM,KAAK,UAAU,MAAM,OAAA;AAC3B,UAAM,WAAqB;AAAA,MACzB;AAAA,MACA,MAAM,UAAU,QAAQ,MAAM,EAAE;AAAA,MAChC,aAAa,UAAU,eAAe,gBAAgB,OAAO;AAAA,MAC7D,SAAS,UAAU,WAAW,CAAA;AAAA,MAC9B,eAAe,UAAU,iBAAiB,CAAA;AAAA,MAC1C,YAAY;AAAA,MACZ,MAAM,UAAU;AAAA,MAChB,iBAAiB,UAAU;AAAA,MAC3B,eAAe,UAAU;AAAA,MACzB,aAAa,UAAU;AAAA,IAAA;AAEzB,SAAK,GAAG,eAAe,QAAQ;AAC/B,WAAO;AAAA,EACT;AAAA,EAEA,SAAS,WAAsC;AAC7C,SAAK,YAAY,QAAQ,SAAS;AAAA,EACpC;AACF;AAGO,SAAS,yBAA+B;AAC7C,YAAU;AACZ;"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { AppApprovalMethod, AppCatalogData, GroupingTagDefinition, Resource } from '@igstack/app-catalog-backend-core';
|
|
2
|
+
export declare class MockDb {
|
|
3
|
+
resources: Resource[];
|
|
4
|
+
tagsDefinitions: GroupingTagDefinition[];
|
|
5
|
+
approvalMethods: AppApprovalMethod[];
|
|
6
|
+
upsertResource(resource: Resource): void;
|
|
7
|
+
/** @deprecated Use upsertResource */
|
|
8
|
+
upsertApp(app: Resource): void;
|
|
9
|
+
getResources(): Resource[];
|
|
10
|
+
/** @deprecated Use getResources */
|
|
11
|
+
getApps(): Resource[];
|
|
12
|
+
getResource(slug: string): Resource;
|
|
13
|
+
/** @deprecated Use getResource */
|
|
14
|
+
getApp(slug: string): Resource;
|
|
15
|
+
setTagDefinitions(defs: GroupingTagDefinition[]): void;
|
|
16
|
+
setApprovalMethods(methods: AppApprovalMethod[]): void;
|
|
17
|
+
getAppCatalogData(): AppCatalogData;
|
|
18
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
class MockDb {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.resources = [];
|
|
4
|
+
this.tagsDefinitions = [];
|
|
5
|
+
this.approvalMethods = [];
|
|
6
|
+
}
|
|
7
|
+
upsertResource(resource) {
|
|
8
|
+
this.resources = [
|
|
9
|
+
...this.resources.filter((r) => r.id !== resource.id),
|
|
10
|
+
resource
|
|
11
|
+
];
|
|
12
|
+
}
|
|
13
|
+
/** @deprecated Use upsertResource */
|
|
14
|
+
upsertApp(app) {
|
|
15
|
+
this.upsertResource(app);
|
|
16
|
+
}
|
|
17
|
+
getResources() {
|
|
18
|
+
return this.resources;
|
|
19
|
+
}
|
|
20
|
+
/** @deprecated Use getResources */
|
|
21
|
+
getApps() {
|
|
22
|
+
return this.getResources();
|
|
23
|
+
}
|
|
24
|
+
getResource(slug) {
|
|
25
|
+
const resource = this.resources.find((r) => r.slug === slug);
|
|
26
|
+
if (!resource) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
`MockDb: resource with slug "${slug}" not found. Available: ${this.resources.map((r) => r.slug).join(", ")}`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
return resource;
|
|
32
|
+
}
|
|
33
|
+
/** @deprecated Use getResource */
|
|
34
|
+
getApp(slug) {
|
|
35
|
+
return this.getResource(slug);
|
|
36
|
+
}
|
|
37
|
+
setTagDefinitions(defs) {
|
|
38
|
+
this.tagsDefinitions = defs;
|
|
39
|
+
}
|
|
40
|
+
setApprovalMethods(methods) {
|
|
41
|
+
this.approvalMethods = methods;
|
|
42
|
+
}
|
|
43
|
+
getAppCatalogData() {
|
|
44
|
+
return {
|
|
45
|
+
resources: this.resources,
|
|
46
|
+
tagsDefinitions: this.tagsDefinitions,
|
|
47
|
+
approvalMethods: this.approvalMethods,
|
|
48
|
+
persons: [],
|
|
49
|
+
groups: []
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export {
|
|
54
|
+
MockDb
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=MockDb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MockDb.js","sources":["../../../src/mock-backend/MockDb.ts"],"sourcesContent":["import type {\n AppApprovalMethod,\n AppCatalogData,\n GroupingTagDefinition,\n Resource,\n} from '@igstack/app-catalog-backend-core'\n\nexport class MockDb {\n resources: Resource[] = []\n tagsDefinitions: GroupingTagDefinition[] = []\n approvalMethods: AppApprovalMethod[] = []\n\n upsertResource(resource: Resource): void {\n this.resources = [\n ...this.resources.filter((r) => r.id !== resource.id),\n resource,\n ]\n }\n\n /** @deprecated Use upsertResource */\n upsertApp(app: Resource): void {\n this.upsertResource(app)\n }\n\n getResources(): Resource[] {\n return this.resources\n }\n\n /** @deprecated Use getResources */\n getApps(): Resource[] {\n return this.getResources()\n }\n\n getResource(slug: string): Resource {\n const resource = this.resources.find((r) => r.slug === slug)\n if (!resource) {\n throw new Error(\n `MockDb: resource with slug \"${slug}\" not found. Available: ${this.resources.map((r) => r.slug).join(', ')}`,\n )\n }\n return resource\n }\n\n /** @deprecated Use getResource */\n getApp(slug: string): Resource {\n return this.getResource(slug)\n }\n\n setTagDefinitions(defs: GroupingTagDefinition[]): void {\n this.tagsDefinitions = defs\n }\n\n setApprovalMethods(methods: AppApprovalMethod[]): void {\n this.approvalMethods = methods\n }\n\n getAppCatalogData(): AppCatalogData {\n return {\n resources: this.resources,\n tagsDefinitions: this.tagsDefinitions,\n approvalMethods: this.approvalMethods,\n persons: [],\n groups: [],\n }\n }\n}\n"],"names":[],"mappings":"AAOO,MAAM,OAAO;AAAA,EAAb,cAAA;AACL,SAAA,YAAwB,CAAA;AACxB,SAAA,kBAA2C,CAAA;AAC3C,SAAA,kBAAuC,CAAA;AAAA,EAAC;AAAA,EAExC,eAAe,UAA0B;AACvC,SAAK,YAAY;AAAA,MACf,GAAG,KAAK,UAAU,OAAO,CAAC,MAAM,EAAE,OAAO,SAAS,EAAE;AAAA,MACpD;AAAA,IAAA;AAAA,EAEJ;AAAA;AAAA,EAGA,UAAU,KAAqB;AAC7B,SAAK,eAAe,GAAG;AAAA,EACzB;AAAA,EAEA,eAA2B;AACzB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,UAAsB;AACpB,WAAO,KAAK,aAAA;AAAA,EACd;AAAA,EAEA,YAAY,MAAwB;AAClC,UAAM,WAAW,KAAK,UAAU,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AAC3D,QAAI,CAAC,UAAU;AACb,YAAM,IAAI;AAAA,QACR,+BAA+B,IAAI,2BAA2B,KAAK,UAAU,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;AAAA,MAAA;AAAA,IAE9G;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,OAAO,MAAwB;AAC7B,WAAO,KAAK,YAAY,IAAI;AAAA,EAC9B;AAAA,EAEA,kBAAkB,MAAqC;AACrD,SAAK,kBAAkB;AAAA,EACzB;AAAA,EAEA,mBAAmB,SAAoC;AACrD,SAAK,kBAAkB;AAAA,EACzB;AAAA,EAEA,oBAAoC;AAClC,WAAO;AAAA,MACL,WAAW,KAAK;AAAA,MAChB,iBAAiB,KAAK;AAAA,MACtB,iBAAiB,KAAK;AAAA,MACtB,SAAS,CAAA;AAAA,MACT,QAAQ,CAAA;AAAA,IAAC;AAAA,EAEb;AACF;"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AppCatalogData } from '@igstack/app-catalog-backend-core';
|
|
2
|
+
import { MockDb } from './MockDb.js';
|
|
3
|
+
import { MockUserContext } from './MockUserContext.js';
|
|
4
|
+
export declare class MockService {
|
|
5
|
+
readonly db: MockDb;
|
|
6
|
+
readonly userContext: MockUserContext;
|
|
7
|
+
constructor(db: MockDb, userContext: MockUserContext);
|
|
8
|
+
getAppCatalogData(): AppCatalogData;
|
|
9
|
+
getSessionResponse(): object | null;
|
|
10
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class MockService {
|
|
2
|
+
constructor(db, userContext) {
|
|
3
|
+
this.db = db;
|
|
4
|
+
this.userContext = userContext;
|
|
5
|
+
}
|
|
6
|
+
getAppCatalogData() {
|
|
7
|
+
return this.db.getAppCatalogData();
|
|
8
|
+
}
|
|
9
|
+
getSessionResponse() {
|
|
10
|
+
return this.userContext.getSessionResponse();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
MockService
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=MockService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MockService.js","sources":["../../../src/mock-backend/MockService.ts"],"sourcesContent":["import type { AppCatalogData } from '@igstack/app-catalog-backend-core'\nimport type { MockDb } from './MockDb'\nimport type { MockUserContext } from './MockUserContext'\n\nexport class MockService {\n constructor(\n readonly db: MockDb,\n readonly userContext: MockUserContext,\n ) {}\n\n getAppCatalogData(): AppCatalogData {\n return this.db.getAppCatalogData()\n }\n\n getSessionResponse(): object | null {\n return this.userContext.getSessionResponse()\n }\n}\n"],"names":[],"mappings":"AAIO,MAAM,YAAY;AAAA,EACvB,YACW,IACA,aACT;AAFS,SAAA,KAAA;AACA,SAAA,cAAA;AAAA,EACR;AAAA,EAEH,oBAAoC;AAClC,WAAO,KAAK,GAAG,kBAAA;AAAA,EACjB;AAAA,EAEA,qBAAoC;AAClC,WAAO,KAAK,YAAY,mBAAA;AAAA,EAC1B;AACF;"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface UserConfig {
|
|
2
|
+
name: string;
|
|
3
|
+
isAdmin: boolean;
|
|
4
|
+
isAuthenticated: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare class MockUserContext {
|
|
7
|
+
private user;
|
|
8
|
+
setUser(overrides: Partial<UserConfig>): void;
|
|
9
|
+
getUser(): UserConfig;
|
|
10
|
+
getSessionResponse(): object | null;
|
|
11
|
+
}
|