@igstack/app-catalog-frontend-core 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/__tests__/integration/tools/AppDetailTools.d.ts +0 -5
- package/package.json +4 -4
- package/src/__tests__/integration/appCatalog.integration.test.ts +0 -73
- package/src/__tests__/integration/appDeepLink.integration.test.ts +1 -24
- package/src/__tests__/integration/appFreshness.integration.test.ts +0 -15
- package/src/__tests__/integration/tools/AppDetailTools.ts +0 -11
|
@@ -41,11 +41,6 @@ export declare class AppDetailTools {
|
|
|
41
41
|
clickViewReplacement(): Promise<void>;
|
|
42
42
|
/** The title shown in the currently open detail panel (empty if none). */
|
|
43
43
|
getOpenTitle(): string;
|
|
44
|
-
/**
|
|
45
|
-
* The prominent primary "Open" button in the app detail card.
|
|
46
|
-
* Returns the anchor element when the app has an appUrl, null otherwise.
|
|
47
|
-
*/
|
|
48
|
-
getOpenButton(): HTMLAnchorElement | null;
|
|
49
44
|
private getPanel;
|
|
50
45
|
private findHeading;
|
|
51
46
|
private getNextSiblingText;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@igstack/app-catalog-frontend-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Frontend core library for App Catalog",
|
|
5
5
|
"homepage": "https://github.com/lislon/app-catalog",
|
|
6
6
|
"repository": {
|
|
@@ -134,8 +134,8 @@
|
|
|
134
134
|
"vite-plugin-static-copy": "^3.1.4",
|
|
135
135
|
"vite-plugin-svgr": "^4.2.0",
|
|
136
136
|
"vitest": "^4.1.2",
|
|
137
|
-
"@igstack/app-catalog-backend-core": "0.
|
|
138
|
-
"@igstack/app-catalog-shared-core": "0.
|
|
137
|
+
"@igstack/app-catalog-backend-core": "0.16.0",
|
|
138
|
+
"@igstack/app-catalog-shared-core": "0.16.0"
|
|
139
139
|
},
|
|
140
140
|
"peerDependencies": {
|
|
141
141
|
"react": "19.1.2",
|
|
@@ -144,7 +144,7 @@
|
|
|
144
144
|
"vite": "^6.3.5",
|
|
145
145
|
"vite-plugin-svgr": "^4.2.0"
|
|
146
146
|
},
|
|
147
|
-
"gitHead": "
|
|
147
|
+
"gitHead": "8ef46d75c527a6d4a00965f7529e000c03cf0bc6",
|
|
148
148
|
"scripts": {
|
|
149
149
|
"build": "vite build",
|
|
150
150
|
"build:lenient": "vite build --mode lenient",
|
|
@@ -148,36 +148,6 @@ describe('App Catalog Integration', () => {
|
|
|
148
148
|
expect(ui.catalog.isDetailPanelOpen()).toBe(false)
|
|
149
149
|
})
|
|
150
150
|
|
|
151
|
-
// Detail card UX (#45): the detail panel shows a prominent primary "Open" button
|
|
152
|
-
// with the destination URL visible alongside it.
|
|
153
|
-
it('shows a prominent primary Open button with URL in the app detail card', async () => {
|
|
154
|
-
const { ui } = await given(
|
|
155
|
-
magazine.custom(({ backendCfg }) => {
|
|
156
|
-
backendCfg.withApp({
|
|
157
|
-
displayName: 'Website App',
|
|
158
|
-
description: 'Has a website',
|
|
159
|
-
appUrl: 'https://example.natera.com/app',
|
|
160
|
-
})
|
|
161
|
-
backendCfg.withApp({
|
|
162
|
-
displayName: 'No URL App',
|
|
163
|
-
description: 'No url',
|
|
164
|
-
})
|
|
165
|
-
}),
|
|
166
|
-
)
|
|
167
|
-
|
|
168
|
-
await ui.catalog.openApp('Website App')
|
|
169
|
-
const btn = ui.app.getOpenButton()
|
|
170
|
-
expect(btn).not.toBeNull()
|
|
171
|
-
expect(btn!.getAttribute('href')).toBe('https://example.natera.com/app')
|
|
172
|
-
expect(btn!.getAttribute('target')).toBe('_blank')
|
|
173
|
-
// The URL is shown in the button (stripped of protocol).
|
|
174
|
-
expect(btn!.textContent).toContain('example.natera.com/app')
|
|
175
|
-
|
|
176
|
-
// An app without appUrl shows no Open button.
|
|
177
|
-
await ui.catalog.openApp('No URL App')
|
|
178
|
-
expect(ui.app.getOpenButton()).toBeNull()
|
|
179
|
-
})
|
|
180
|
-
|
|
181
151
|
// Test 5: Returning user — cached data + no onboarding + backend down
|
|
182
152
|
it('returning user sees cached apps even when backend is unavailable', async () => {
|
|
183
153
|
suppressConsole([/TRPC Error/, /Failed to fetch/])
|
|
@@ -328,47 +298,4 @@ describe('App Catalog Integration', () => {
|
|
|
328
298
|
/(?<![\d])0(?![\d]).*clear filters/i,
|
|
329
299
|
)
|
|
330
300
|
})
|
|
331
|
-
|
|
332
|
-
it('highlights matched query text in search result app names (#57)', async () => {
|
|
333
|
-
const { ui } = await given(
|
|
334
|
-
magazine.custom(({ backendCfg }) => {
|
|
335
|
-
backendCfg.withApp({ displayName: 'Kubernetes Platform' })
|
|
336
|
-
backendCfg.withApp({ displayName: 'Other Tool' })
|
|
337
|
-
}),
|
|
338
|
-
)
|
|
339
|
-
|
|
340
|
-
await ui.catalog.search('kube')
|
|
341
|
-
|
|
342
|
-
await waitFor(() => {
|
|
343
|
-
const marks = document.querySelectorAll('mark')
|
|
344
|
-
const markTexts = [...marks].map((m) => String(m.textContent))
|
|
345
|
-
expect(markTexts.some((t) => t.toLowerCase().includes('kube'))).toBe(true)
|
|
346
|
-
})
|
|
347
|
-
})
|
|
348
|
-
|
|
349
|
-
it('clear (×) button appears when search has text and clears on click (#54)', async () => {
|
|
350
|
-
const { ui } = await given(magazine.full())
|
|
351
|
-
|
|
352
|
-
// Before typing: no clear button
|
|
353
|
-
expect(
|
|
354
|
-
document.querySelector('[aria-label="Clear search"]'),
|
|
355
|
-
).not.toBeInTheDocument()
|
|
356
|
-
|
|
357
|
-
// After typing: clear button appears
|
|
358
|
-
await ui.catalog.search('jira')
|
|
359
|
-
await waitFor(() => {
|
|
360
|
-
expect(
|
|
361
|
-
document.querySelector('[aria-label="Clear search"]'),
|
|
362
|
-
).toBeInTheDocument()
|
|
363
|
-
})
|
|
364
|
-
|
|
365
|
-
// Click × — input clears, button disappears
|
|
366
|
-
fireEvent.click(document.querySelector('[aria-label="Clear search"]')!)
|
|
367
|
-
await waitFor(() => {
|
|
368
|
-
expect(ui.catalog.getSearchInput().value).toBe('')
|
|
369
|
-
expect(
|
|
370
|
-
document.querySelector('[aria-label="Clear search"]'),
|
|
371
|
-
).not.toBeInTheDocument()
|
|
372
|
-
})
|
|
373
|
-
})
|
|
374
301
|
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { fireEvent,
|
|
2
|
+
import { fireEvent, waitFor } from '@testing-library/react'
|
|
3
3
|
import '@testing-library/jest-dom/vitest'
|
|
4
4
|
|
|
5
5
|
import { given } from './harness/given'
|
|
@@ -45,29 +45,6 @@ describe('App deep-link routing', () => {
|
|
|
45
45
|
expect(router.state.location.pathname).toBe('/')
|
|
46
46
|
})
|
|
47
47
|
|
|
48
|
-
it('Esc closes card opened by mouse click (#53 bug 1)', async () => {
|
|
49
|
-
const { ui, router } = await given(magazine.full(), { initialRoute: '/' })
|
|
50
|
-
// Open via click (mouse, not keyboard nav)
|
|
51
|
-
await ui.catalog.openApp('Jira')
|
|
52
|
-
await waitFor(() => expect(ui.catalog.isDetailPanelOpen()).toBe(true))
|
|
53
|
-
// Esc should close — no "dead click" needed
|
|
54
|
-
fireEvent.keyDown(document, { key: 'Escape', code: 'Escape' })
|
|
55
|
-
await waitFor(() => expect(ui.catalog.isDetailPanelOpen()).toBe(false))
|
|
56
|
-
expect(router.state.location.pathname).toBe('/')
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
it('closes the detail panel when the × close button is clicked (#59)', async () => {
|
|
60
|
-
const { ui, router } = await given(magazine.full(), { initialRoute: '/' })
|
|
61
|
-
await ui.catalog.openApp('Jira')
|
|
62
|
-
await waitFor(() => expect(ui.catalog.isDetailPanelOpen()).toBe(true))
|
|
63
|
-
const closeBtn = screen.getByRole('button', { name: /^Close$/i })
|
|
64
|
-
fireEvent.click(closeBtn)
|
|
65
|
-
await waitFor(() => {
|
|
66
|
-
expect(ui.catalog.isDetailPanelOpen()).toBe(false)
|
|
67
|
-
})
|
|
68
|
-
expect(router.state.location.pathname).toBe('/')
|
|
69
|
-
})
|
|
70
|
-
|
|
71
48
|
it('ignores legacy ?app= query param (no detail opens)', async () => {
|
|
72
49
|
const { ui } = await given(magazine.full(), { initialRoute: '/?app=jira' })
|
|
73
50
|
await waitFor(() => {
|
|
@@ -45,21 +45,6 @@ describe('App freshness line', () => {
|
|
|
45
45
|
expect(screen.getByText(/may be out of date/i)).toBeInTheDocument()
|
|
46
46
|
})
|
|
47
47
|
|
|
48
|
-
it('shows "Added" date before Sources when createdAt is present (#55)', async () => {
|
|
49
|
-
const { ui } = await given(
|
|
50
|
-
magazine.full(({ backendCfg }) => {
|
|
51
|
-
backendCfg.withApp({
|
|
52
|
-
slug: 'dated-app',
|
|
53
|
-
displayName: 'Dated App',
|
|
54
|
-
createdAt: iso(30 * DAY),
|
|
55
|
-
})
|
|
56
|
-
}),
|
|
57
|
-
{ initialRoute: '/app/dated-app' },
|
|
58
|
-
)
|
|
59
|
-
await waitFor(() => expect(ui.catalog.isDetailPanelOpen()).toBe(true))
|
|
60
|
-
expect(screen.getByText(/Added/i)).toBeInTheDocument()
|
|
61
|
-
})
|
|
62
|
-
|
|
63
48
|
it('shows no freshness line when the app was never scanned', async () => {
|
|
64
49
|
const { ui } = await given(
|
|
65
50
|
magazine.full(({ backendCfg }) => {
|
|
@@ -152,17 +152,6 @@ export class AppDetailTools {
|
|
|
152
152
|
return panel.querySelector('.text-2xl')?.textContent.trim() ?? ''
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
/**
|
|
156
|
-
* The prominent primary "Open" button in the app detail card.
|
|
157
|
-
* Returns the anchor element when the app has an appUrl, null otherwise.
|
|
158
|
-
*/
|
|
159
|
-
getOpenButton(): HTMLAnchorElement | null {
|
|
160
|
-
const panel = this.getPanel()
|
|
161
|
-
return (
|
|
162
|
-
panel.querySelector<HTMLAnchorElement>('[aria-label^="Open "]') ?? null
|
|
163
|
-
)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
155
|
private getPanel(): HTMLElement {
|
|
167
156
|
const closeButton = screen.queryByLabelText('Close details panel')
|
|
168
157
|
if (!closeButton) {
|