@backstage/frontend-test-utils
Advanced tools
Comparing version 0.0.0-nightly-20240801022222 to 0.0.0-nightly-20240802021846
# @backstage/frontend-test-utils | ||
## 0.0.0-nightly-20240801022222 | ||
## 0.0.0-nightly-20240802021846 | ||
### Patch Changes | ||
- 8209449: Added new APIs for testing extensions | ||
- 3be9aeb: Added support for v2 extensions, which declare their inputs and outputs without using a data map. | ||
- 6349099: Added config input type to the extensions | ||
- Updated dependencies | ||
- @backstage/frontend-plugin-api@0.0.0-nightly-20240801022222 | ||
- @backstage/frontend-app-api@0.0.0-nightly-20240801022222 | ||
- @backstage/test-utils@0.0.0-nightly-20240801022222 | ||
- @backstage/frontend-plugin-api@0.0.0-nightly-20240802021846 | ||
- @backstage/frontend-app-api@0.0.0-nightly-20240802021846 | ||
- @backstage/config@1.2.0 | ||
- @backstage/test-utils@0.0.0-nightly-20240802021846 | ||
- @backstage/types@1.1.1 | ||
@@ -14,0 +16,0 @@ |
@@ -6,5 +6,9 @@ import React from 'react'; | ||
import { createExtension, createExtensionInput, createNavItemExtension, coreExtensionData, useRouteRef, createExtensionOverrides, createRouterExtension } from '@backstage/frontend-plugin-api'; | ||
import { MockConfigApi } from '@backstage/test-utils'; | ||
import { ConfigReader } from '@backstage/config'; | ||
import { resolveExtensionDefinition } from '../frontend-plugin-api/src/wiring/resolveExtensionDefinition.esm.js'; | ||
import { toInternalExtensionDefinition } from '../frontend-plugin-api/src/wiring/createExtension.esm.js'; | ||
import { resolveAppTree } from '../frontend-app-api/src/tree/resolveAppTree.esm.js'; | ||
import { resolveAppNodeSpecs } from '../frontend-app-api/src/tree/resolveAppNodeSpecs.esm.js'; | ||
import { instantiateAppNodeTree } from '../frontend-app-api/src/tree/instantiateAppNodeTree.esm.js'; | ||
import { readAppExtensionsConfig } from '../frontend-app-api/src/tree/readAppExtensionsConfig.esm.js'; | ||
@@ -42,2 +46,23 @@ const NavItem = (props) => { | ||
}); | ||
class ExtensionQuery { | ||
#node; | ||
constructor(node) { | ||
this.#node = node; | ||
} | ||
get node() { | ||
return this.#node; | ||
} | ||
get instance() { | ||
const instance = this.#node.instance; | ||
if (!instance) { | ||
throw new Error( | ||
`Unable to access the instance of extension with ID '${this.#node.spec.id}'` | ||
); | ||
} | ||
return instance; | ||
} | ||
data(ref) { | ||
return this.instance.getData(ref); | ||
} | ||
} | ||
class ExtensionTester { | ||
@@ -84,4 +109,10 @@ /** @internal */ | ||
} | ||
#tree; | ||
#extensions = new Array(); | ||
add(extension, options) { | ||
if (this.#tree) { | ||
throw new Error( | ||
"Cannot add more extensions accessing the extension tree" | ||
); | ||
} | ||
const { name, namespace } = extension; | ||
@@ -93,5 +124,6 @@ const definition = { | ||
}; | ||
const { id } = resolveExtensionDefinition(definition); | ||
const resolvedExtension = resolveExtensionDefinition(definition); | ||
this.#extensions.push({ | ||
id, | ||
id: resolvedExtension.id, | ||
extension: resolvedExtension, | ||
definition, | ||
@@ -102,5 +134,24 @@ config: options?.config | ||
} | ||
data(ref) { | ||
const tree = this.#resolveTree(); | ||
return new ExtensionQuery(tree.root).data(ref); | ||
} | ||
query(id) { | ||
const tree = this.#resolveTree(); | ||
const actualId = typeof id === "string" ? id : resolveExtensionDefinition(id).id; | ||
const node = tree.nodes.get(actualId); | ||
if (!node) { | ||
throw new Error( | ||
`Extension with ID '${actualId}' not found, please make sure it's added to the tester.` | ||
); | ||
} else if (!node.instance) { | ||
throw new Error( | ||
`Extension with ID '${actualId}' has not been instantiated, because it is not part of the test subject's extension tree.` | ||
); | ||
} | ||
return new ExtensionQuery(node); | ||
} | ||
render(options) { | ||
const { config = {} } = options ?? {}; | ||
const [subject, ...rest] = this.#extensions; | ||
const [subject] = this.#extensions; | ||
if (!subject) { | ||
@@ -111,22 +162,2 @@ throw new Error( | ||
} | ||
const extensionsConfig = [ | ||
...rest.map((extension) => ({ | ||
[extension.id]: { | ||
config: extension.config | ||
} | ||
})), | ||
{ | ||
[subject.id]: { | ||
config: subject.config, | ||
disabled: false | ||
} | ||
} | ||
]; | ||
const finalConfig = { | ||
...config, | ||
app: { | ||
...typeof config.app === "object" ? config.app : void 0, | ||
extensions: extensionsConfig | ||
} | ||
}; | ||
const app = createSpecializedApp({ | ||
@@ -145,6 +176,55 @@ features: [ | ||
], | ||
config: new MockConfigApi(finalConfig) | ||
config: this.#getConfig(config) | ||
}); | ||
return render(app.createRoot()); | ||
} | ||
#resolveTree() { | ||
if (this.#tree) { | ||
return this.#tree; | ||
} | ||
const [subject] = this.#extensions; | ||
if (!subject) { | ||
throw new Error( | ||
"No subject found. At least one extension should be added to the tester." | ||
); | ||
} | ||
const tree = resolveAppTree( | ||
subject.id, | ||
resolveAppNodeSpecs({ | ||
features: [], | ||
builtinExtensions: this.#extensions.map((_) => _.extension), | ||
parameters: readAppExtensionsConfig(this.#getConfig()) | ||
}) | ||
); | ||
instantiateAppNodeTree(tree.root); | ||
this.#tree = tree; | ||
return tree; | ||
} | ||
#getConfig(additionalConfig) { | ||
const [subject, ...rest] = this.#extensions; | ||
const extensionsConfig = [ | ||
...rest.map((extension) => ({ | ||
[extension.id]: { | ||
config: extension.config | ||
} | ||
})), | ||
{ | ||
[subject.id]: { | ||
config: subject.config, | ||
disabled: false | ||
} | ||
} | ||
]; | ||
return ConfigReader.fromConfigs([ | ||
{ context: "render-config", data: additionalConfig ?? {} }, | ||
{ | ||
context: "test", | ||
data: { | ||
app: { | ||
extensions: extensionsConfig | ||
} | ||
} | ||
} | ||
]); | ||
} | ||
} | ||
@@ -155,3 +235,3 @@ function createExtensionTester(subject, options) { | ||
export { ExtensionTester, createExtensionTester }; | ||
export { ExtensionQuery, ExtensionTester, createExtensionTester }; | ||
//# sourceMappingURL=createExtensionTester.esm.js.map |
import { toInternalExtensionDefinition } from './createExtension.esm.js'; | ||
function toInternalExtension(overrides) { | ||
const internal = overrides; | ||
if (internal.$$type !== "@backstage/Extension") { | ||
throw new Error( | ||
`Invalid extension instance, bad type '${internal.$$type}'` | ||
); | ||
} | ||
const version = internal.version; | ||
if (version !== "v1" && version !== "v2") { | ||
throw new Error(`Invalid extension instance, bad version '${version}'`); | ||
} | ||
return internal; | ||
} | ||
function resolveExtensionDefinition(definition, context) { | ||
@@ -25,3 +38,3 @@ const internalDefinition = toInternalExtensionDefinition(definition); | ||
export { resolveExtensionDefinition }; | ||
export { resolveExtensionDefinition, toInternalExtension }; | ||
//# sourceMappingURL=resolveExtensionDefinition.esm.js.map |
/// <reference types="react" /> | ||
export { ErrorWithContext, MockConfigApi, MockErrorApi, MockErrorApiOptions, MockFetchApi, MockFetchApiOptions, MockPermissionApi, MockStorageApi, MockStorageBucket, TestApiProvider, TestApiProviderProps, TestApiRegistry, registerMswTestHooks, withLogCollector } from '@backstage/test-utils'; | ||
import { AnalyticsApi, AnalyticsEvent, ExtensionDefinition, RouteRef } from '@backstage/frontend-plugin-api'; | ||
import * as _backstage_frontend_plugin_api from '@backstage/frontend-plugin-api'; | ||
import { AnalyticsApi, AnalyticsEvent, AppNode, ExtensionDataRef, ExtensionDefinition, RouteRef } from '@backstage/frontend-plugin-api'; | ||
import * as _testing_library_react from '@testing-library/react'; | ||
@@ -31,2 +32,10 @@ import { RenderResult } from '@testing-library/react'; | ||
/** @public */ | ||
declare class ExtensionQuery { | ||
#private; | ||
constructor(node: AppNode); | ||
get node(): AppNode; | ||
get instance(): _backstage_frontend_plugin_api.AppNodeInstance; | ||
data<T>(ref: ExtensionDataRef<T>): T | undefined; | ||
} | ||
/** @public */ | ||
declare class ExtensionTester { | ||
@@ -37,2 +46,4 @@ #private; | ||
}): ExtensionTester; | ||
data<T>(ref: ExtensionDataRef<T>): T | undefined; | ||
query(id: string | ExtensionDefinition<any, any>): ExtensionQuery; | ||
render(options?: { | ||
@@ -78,2 +89,2 @@ config?: JsonObject; | ||
export { ExtensionTester, MockAnalyticsApi, type TestAppOptions, createExtensionTester, renderInTestApp, setupRequestMockHandlers }; | ||
export { ExtensionQuery, ExtensionTester, MockAnalyticsApi, type TestAppOptions, createExtensionTester, renderInTestApp, setupRequestMockHandlers }; |
{ | ||
"name": "@backstage/frontend-test-utils", | ||
"version": "0.0.0-nightly-20240801022222", | ||
"version": "0.0.0-nightly-20240802021846", | ||
"backstage": { | ||
@@ -34,9 +34,10 @@ "role": "web-library" | ||
"dependencies": { | ||
"@backstage/frontend-app-api": "^0.0.0-nightly-20240801022222", | ||
"@backstage/frontend-plugin-api": "^0.0.0-nightly-20240801022222", | ||
"@backstage/test-utils": "^0.0.0-nightly-20240801022222", | ||
"@backstage/config": "^1.2.0", | ||
"@backstage/frontend-app-api": "^0.0.0-nightly-20240802021846", | ||
"@backstage/frontend-plugin-api": "^0.0.0-nightly-20240802021846", | ||
"@backstage/test-utils": "^0.0.0-nightly-20240802021846", | ||
"@backstage/types": "^1.1.1" | ||
}, | ||
"devDependencies": { | ||
"@backstage/cli": "^0.0.0-nightly-20240801022222", | ||
"@backstage/cli": "^0.0.0-nightly-20240802021846", | ||
"@testing-library/jest-dom": "^6.0.0", | ||
@@ -43,0 +44,0 @@ "@types/react": "*" |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
152497
30
1060
8
1
1