diff --git a/apps/teams-test-app/src/components/privateApis/ExternalAppCardActionsAPIs.tsx b/apps/teams-test-app/src/components/privateApis/ExternalAppCardActionsAPIs.tsx index e551bba154..4d1c6c3de3 100644 --- a/apps/teams-test-app/src/components/privateApis/ExternalAppCardActionsAPIs.tsx +++ b/apps/teams-test-app/src/components/privateApis/ExternalAppCardActionsAPIs.tsx @@ -1,4 +1,4 @@ -import { externalAppCardActions } from '@microsoft/teams-js'; +import { externalAppCardActions, IAdaptiveCardActionSubmit } from '@microsoft/teams-js'; import React from 'react'; import { ApiWithoutInput, ApiWithTextInput } from '../utils'; @@ -15,7 +15,7 @@ const CheckExternalAppCardActionsCapability = (): React.ReactElement => const ProcessActionSubmit = (): React.ReactElement => ApiWithTextInput<{ appId: string; - actionSubmitPayload: externalAppCardActions.IAdaptiveCardActionSubmit; + actionSubmitPayload: IAdaptiveCardActionSubmit; }>({ name: 'processActionSubmit', title: 'Process Action Submit', diff --git a/apps/teams-test-app/src/components/privateApis/ExternalAppCardActionsForCEAAPIs.tsx b/apps/teams-test-app/src/components/privateApis/ExternalAppCardActionsForCEAAPIs.tsx new file mode 100644 index 0000000000..282fc84f7f --- /dev/null +++ b/apps/teams-test-app/src/components/privateApis/ExternalAppCardActionsForCEAAPIs.tsx @@ -0,0 +1,92 @@ +import { AppId, externalAppCardActionsForCEA, IAdaptiveCardActionSubmit } from '@microsoft/teams-js'; +import React from 'react'; + +import { ApiWithoutInput, ApiWithTextInput } from '../utils'; +import { ModuleWrapper } from '../utils/ModuleWrapper'; + +const CheckExternalAppCardActionsForCEACapability = (): React.ReactElement => + ApiWithoutInput({ + name: 'checkExternalAppCardActionsForCEACapability', + title: 'Check External App Card Actions For CEA Capability', + onClick: async () => + `External App Card Actions For CEA module ${ + externalAppCardActionsForCEA.isSupported() ? 'is' : 'is not' + } supported`, + }); + +const CECProcessActionSubmit = (): React.ReactElement => + ApiWithTextInput<{ + appId: AppId; + conversationId: string; + actionSubmitPayload: IAdaptiveCardActionSubmit; + }>({ + name: 'processActionSubmitForCEA', + title: 'Process Action Submit For CEA', + onClick: { + validateInput: (input) => { + if (!input.appId) { + throw new Error('appId is required'); + } + if (!input.actionSubmitPayload) { + throw new Error('actionSubmitPayload is required'); + } + }, + submit: async (input) => { + await externalAppCardActionsForCEA.processActionSubmit( + input.appId, + input.conversationId, + input.actionSubmitPayload, + ); + return 'Completed'; + }, + }, + defaultInput: JSON.stringify({ + appId: 'b7f8c0a0-6c1d-4a9a-9c0a-2c3f1c0a3b0a', + actionSubmitPayload: { + id: 'submitId', + data: 'data1', + }, + }), + }); + +const CECProcessActionOpenUrl = (): React.ReactElement => + ApiWithTextInput<{ + appId: AppId; + conversationId: string; + url: string; + }>({ + name: 'processActionOpenUrlForCEA', + title: 'Process Action Open Url For CEA', + onClick: { + validateInput: (input) => { + if (!input.appId) { + throw new Error('appId is required'); + } + if (!input.url) { + throw new Error('url is required'); + } + }, + submit: async (input) => { + const result = await externalAppCardActionsForCEA.processActionOpenUrl( + input.appId, + input.conversationId, + new URL(input.url), + ); + return JSON.stringify(result); + }, + }, + defaultInput: JSON.stringify({ + appId: 'b7f8c0a0-6c1d-4a9a-9c0a-2c3f1c0a3b0a', + url: 'https://www.example.com', + }), + }); + +const ExternalAppCardActionsForCEAAPIs = (): React.ReactElement => ( + + + + + +); + +export default ExternalAppCardActionsForCEAAPIs; diff --git a/apps/teams-test-app/src/pages/TestApp.tsx b/apps/teams-test-app/src/pages/TestApp.tsx index 8e24066539..f8264834ae 100644 --- a/apps/teams-test-app/src/pages/TestApp.tsx +++ b/apps/teams-test-app/src/pages/TestApp.tsx @@ -41,6 +41,7 @@ import ChatAPIs from '../components/privateApis/ChatAPIs'; import CopilotAPIs from '../components/privateApis/CopilotAPIs'; import ExternalAppAuthenticationAPIs from '../components/privateApis/ExternalAppAuthenticationAPIs'; import ExternalAppCardActionsAPIs from '../components/privateApis/ExternalAppCardActionsAPIs'; +import ExternalAppCardActionsForCEAAPIs from '../components/privateApis/ExternalAppCardActionsForCEAAPIs'; import ExternalAppCommandsAPIs from '../components/privateApis/ExternalAppCommandsAPIs'; import FilesAPIs from '../components/privateApis/FilesAPIs'; import FullTrustAPIs from '../components/privateApis/FullTrustAPIs'; @@ -114,6 +115,7 @@ export const TestApp: React.FC = () => { + diff --git a/change/@microsoft-teams-js-75e3c524-ba26-4b6a-b01b-9a1f0e315d91.json b/change/@microsoft-teams-js-75e3c524-ba26-4b6a-b01b-9a1f0e315d91.json new file mode 100644 index 0000000000..dc8114ff26 --- /dev/null +++ b/change/@microsoft-teams-js-75e3c524-ba26-4b6a-b01b-9a1f0e315d91.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Added APIs for `externalAppCardActionsForCEA` capability.", + "packageName": "@microsoft/teams-js", + "email": "maggiegong@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/teams-js/src/internal/telemetry.ts b/packages/teams-js/src/internal/telemetry.ts index 2bcbc4172b..6338e8e7a6 100644 --- a/packages/teams-js/src/internal/telemetry.ts +++ b/packages/teams-js/src/internal/telemetry.ts @@ -125,6 +125,8 @@ export const enum ApiName { ExternalAppAuthentication_AuthenticateWithPowerPlatformConnectorPlugins = 'externalAppAuthentication.authenticateWithPowerPlatformConnectorPlugins', ExternalAppCardActions_ProcessActionOpenUrl = 'externalAppCardActions.processActionOpenUrl', ExternalAppCardActions_ProcessActionSubmit = 'externalAppCardActions.processActionSubmit', + ExternalAppCardActionsForCEA_ProcessActionOpenUrl = 'externalAppCardActionsForCEA.processActionOpenUrl', + ExternalAppCardActionsForCEA_ProcessActionSubmit = 'externalAppCardActionsForCEA.processActionSubmit', ExternalAppCommands_ProcessActionCommands = 'externalAppCommands.processActionCommand', Files_AddCloudStorageFolder = 'files.addCloudStorageFolder', Files_AddCloudStorageProvider = 'files.addCloudStorageProvider', diff --git a/packages/teams-js/src/private/externalAppCardActions.ts b/packages/teams-js/src/private/externalAppCardActions.ts index bdfaf0b2e6..863d7d9b6a 100644 --- a/packages/teams-js/src/private/externalAppCardActions.ts +++ b/packages/teams-js/src/private/externalAppCardActions.ts @@ -4,7 +4,7 @@ import { ApiName, ApiVersionNumber, getApiVersionTag } from '../internal/telemet import { AppId } from '../public'; import { errorNotSupportedOnPlatform, FrameContexts } from '../public/constants'; import { runtime } from '../public/runtime'; -import { ExternalAppErrorCode } from './constants'; +import { ActionOpenUrlError, ActionSubmitError, IAdaptiveCardActionSubmit } from './interfaces'; /** * v2 APIs telemetry file: All of APIs in this capability file should send out API version v2 ONLY @@ -31,54 +31,6 @@ export namespace externalAppCardActions { GenericUrl = 'GenericUrl', } - /** - * @hidden - * Error that can be thrown from IExternalAppCardActionService.handleActionOpenUrl - * - * @internal - * Limited to Microsoft-internal use - */ - export interface ActionOpenUrlError { - errorCode: ActionOpenUrlErrorCode; - message?: string; - } - - /** - * @hidden - * Error codes that can be thrown from IExternalAppCardActionService.handleActionOpenUrl - * @internal - * Limited to Microsoft-internal use - */ - export enum ActionOpenUrlErrorCode { - INTERNAL_ERROR = 'INTERNAL_ERROR', // Generic error - INVALID_LINK = 'INVALID_LINK', // Deep link is invalid - NOT_SUPPORTED = 'NOT_SUPPORTED', // Deep link is not supported - } - - /** - * @hidden - * The payload that is used when executing an Adaptive Card Action.Submit - * @internal - * Limited to Microsoft-internal use - */ - export interface IAdaptiveCardActionSubmit { - id: string; - data: string | Record; - } - - /** - * - * @hidden - * Error that can be thrown from IExternalAppCardActionService.handleActionSubmit - * - * @internal - * Limited to Microsoft-internal use - */ - export interface ActionSubmitError { - errorCode: ExternalAppErrorCode; - message?: string; - } - /** * @beta * @hidden diff --git a/packages/teams-js/src/private/externalAppCardActionsForCEA.ts b/packages/teams-js/src/private/externalAppCardActionsForCEA.ts new file mode 100644 index 0000000000..eea0d73ce8 --- /dev/null +++ b/packages/teams-js/src/private/externalAppCardActionsForCEA.ts @@ -0,0 +1,108 @@ +import { sendAndUnwrap, sendMessageToParentAsync } from '../internal/communication'; +import { ensureInitialized } from '../internal/internalAPIs'; +import { ApiName, ApiVersionNumber, getApiVersionTag } from '../internal/telemetry'; +import { validateId } from '../internal/utils'; +import { AppId } from '../public'; +import { errorNotSupportedOnPlatform, FrameContexts } from '../public/constants'; +import { runtime } from '../public/runtime'; +import { ActionOpenUrlError, ActionOpenUrlType, ActionSubmitError, IAdaptiveCardActionSubmit } from './interfaces'; + +/** + * All of APIs in this capability file should send out API version v2 ONLY + */ +const externalAppCardActionsTelemetryVersionNumber: ApiVersionNumber = ApiVersionNumber.V_2; +/** + * @beta + * @hidden + * Namespace to delegate adaptive card action for Custom Engine Agent execution to the host + * @internal + * Limited to Microsoft-internal use + */ +export namespace externalAppCardActionsForCEA { + /** + * @beta + * @hidden + * Delegates an Adaptive Card Action.OpenUrl request to the host for the application with the provided app ID. + * @internal + * Limited to Microsoft-internal use + * @param appId ID of the application the request is intended for. This must be a UUID + * @param conversationId To tell the bot what conversation the calls are coming from + * @param url The URL to open + * @throws Error if the response has not successfully completed + * @returns Promise that resolves to ActionOpenUrlType indicating the type of URL that was opened on success and rejects with ActionOpenUrlError if the request fails + */ + export async function processActionOpenUrl( + appId: AppId, + conversationId: string, + url: URL, + ): Promise { + ensureInitialized(runtime, FrameContexts.content); + if (!isSupported()) { + throw errorNotSupportedOnPlatform; + } + validateId(conversationId, new Error('conversation id is not valid.')); + const [error, response] = await sendMessageToParentAsync<[ActionOpenUrlError, ActionOpenUrlType]>( + getApiVersionTag( + externalAppCardActionsTelemetryVersionNumber, + ApiName.ExternalAppCardActionsForCEA_ProcessActionOpenUrl, + ), + ApiName.ExternalAppCardActionsForCEA_ProcessActionOpenUrl, + [appId, url.href, conversationId], + ); + if (error) { + throw error; + } else { + return response; + } + } + + /** + * @beta + * @hidden + * Delegates an Adaptive Card Action.Submit request to the host for the application with the provided app ID + * @internal + * Limited to Microsoft-internal use + * @param appId ID of the application the request is intended for. This must be a UUID + * @param conversationId To tell the bot what conversation the calls are coming from + * @param actionSubmitPayload The Adaptive Card Action.Submit payload + * @throws Error if host notifies of an error + * @returns Promise that resolves when the request is completed and rejects with ActionSubmitError if the request fails + */ + export async function processActionSubmit( + appId: AppId, + conversationId: string, + actionSubmitPayload: IAdaptiveCardActionSubmit, + ): Promise { + ensureInitialized(runtime, FrameContexts.content); + if (!isSupported()) { + throw errorNotSupportedOnPlatform; + } + validateId(conversationId, new Error('conversation id is not valid.')); + const error = await sendAndUnwrap( + getApiVersionTag( + externalAppCardActionsTelemetryVersionNumber, + ApiName.ExternalAppCardActionsForCEA_ProcessActionSubmit, + ), + ApiName.ExternalAppCardActionsForCEA_ProcessActionSubmit, + [appId, conversationId, actionSubmitPayload], + ); + if (error) { + throw error; + } + } + + /** + * @beta + * @hidden + * Checks if the externalAppCardActionsForCEA capability is supported by the host + * @returns boolean to represent whether externalAppCardActions capability is supported + * + * @throws Error if {@linkcode app.initialize} has not successfully completed + * + * @internal + * Limited to Microsoft-internal use + */ + export function isSupported(): boolean { + return ensureInitialized(runtime) && runtime.supports.externalAppCardActionsForCEA ? true : false; + } +} diff --git a/packages/teams-js/src/private/index.ts b/packages/teams-js/src/private/index.ts index 178e407cee..64aeb871a3 100644 --- a/packages/teams-js/src/private/index.ts +++ b/packages/teams-js/src/private/index.ts @@ -22,6 +22,7 @@ export { conversations } from './conversations'; export { copilot } from './copilot'; export { externalAppAuthentication } from './externalAppAuthentication'; export { externalAppCardActions } from './externalAppCardActions'; +export { externalAppCardActionsForCEA } from './externalAppCardActionsForCEA'; export { externalAppCommands } from './externalAppCommands'; export { files } from './files'; export { meetingRoom } from './meetingRoom'; diff --git a/packages/teams-js/src/private/interfaces.ts b/packages/teams-js/src/private/interfaces.ts index 7f745e90c5..ecc807f72e 100644 --- a/packages/teams-js/src/private/interfaces.ts +++ b/packages/teams-js/src/private/interfaces.ts @@ -1,4 +1,5 @@ import { FileOpenPreference, TeamInformation } from '../public/interfaces'; +import { ExternalAppErrorCode } from './constants'; /** * @hidden @@ -271,3 +272,74 @@ export interface UserJoinedTeamsInformation { */ userJoinedTeams: TeamInformation[]; } + +/** + * @beta + * @hidden + * The types for ActionOpenUrl + * + * @internal + * Limited to Microsoft-internal use + */ +export enum ActionOpenUrlType { + DeepLinkDialog = 'DeepLinkDialog', + DeepLinkOther = 'DeepLinkOther', + DeepLinkStageView = 'DeepLinkStageView', + GenericUrl = 'GenericUrl', +} + +/** + * @beta + * @hidden + * Error that can be thrown from IExternalAppCardActionService.handleActionOpenUrl + * and IExternalAppCardActionForCEAService.handleActionOpenUrl + * + * @internal + * Limited to Microsoft-internal use + */ +export interface ActionOpenUrlError { + errorCode: ActionOpenUrlErrorCode; + message?: string; +} + +/** + * @beta + * @hidden + * Error codes that can be thrown from IExternalAppCardActionService.handleActionOpenUrl + * and IExternalAppCardActionForCEAService.handleActionOpenUrl + * + * @internal + * Limited to Microsoft-internal use + */ +export enum ActionOpenUrlErrorCode { + INTERNAL_ERROR = 'INTERNAL_ERROR', // Generic error + INVALID_LINK = 'INVALID_LINK', // Deep link is invalid + NOT_SUPPORTED = 'NOT_SUPPORTED', // Deep link is not supported +} + +/** + * @beta + * @hidden + * The payload that is used when executing an Adaptive Card Action.Submit + * + * @internal + * Limited to Microsoft-internal use + */ +export interface IAdaptiveCardActionSubmit { + id: string; + data: string | Record; +} + +/** + * @beta + * @hidden + * Error that can be thrown from IExternalAppCardActionService.handleActionSubmit + * and IExternalAppCardActionForCEAService.handleActionSubmit + * + * @internal + * Limited to Microsoft-internal use + */ +export interface ActionSubmitError { + errorCode: ExternalAppErrorCode; + message?: string; +} diff --git a/packages/teams-js/src/public/runtime.ts b/packages/teams-js/src/public/runtime.ts index 946639d3e3..4aa5e93815 100644 --- a/packages/teams-js/src/public/runtime.ts +++ b/packages/teams-js/src/public/runtime.ts @@ -243,6 +243,7 @@ interface IRuntimeV4 extends IBaseRuntime { }; readonly externalAppAuthentication?: {}; readonly externalAppCardActions?: {}; + readonly externalAppCardActionsForCEA?: {}; readonly externalAppCommands?: {}; readonly geoLocation?: { readonly map?: {}; diff --git a/packages/teams-js/test/private/externalAppCardActions.spec.ts b/packages/teams-js/test/private/externalAppCardActions.spec.ts index 2fd8a7ea85..ad9ecc8bee 100644 --- a/packages/teams-js/test/private/externalAppCardActions.spec.ts +++ b/packages/teams-js/test/private/externalAppCardActions.spec.ts @@ -2,6 +2,7 @@ import { errorLibraryNotInitialized } from '../../src/internal/constants'; import { GlobalVars } from '../../src/internal/globalVars'; import { ExternalAppErrorCode } from '../../src/private/constants'; import { externalAppCardActions } from '../../src/private/externalAppCardActions'; +import { ActionOpenUrlErrorCode } from '../../src/private/interfaces'; import { FrameContexts } from '../../src/public'; import { app } from '../../src/public/app'; import { errorNotSupportedOnPlatform } from '../../src/public/constants'; @@ -127,7 +128,7 @@ describe('externalAppCardActions', () => { const allowedFrameContexts = [FrameContexts.content]; const testUrl = new URL('https://example.com'); const testError = { - errorCode: externalAppCardActions.ActionOpenUrlErrorCode.INTERNAL_ERROR, + errorCode: ActionOpenUrlErrorCode.INTERNAL_ERROR, message: 'testMessage', }; const testResponse = externalAppCardActions.ActionOpenUrlType.DeepLinkDialog; diff --git a/packages/teams-js/test/private/externalAppCardActionsForCEA.spec.ts b/packages/teams-js/test/private/externalAppCardActionsForCEA.spec.ts new file mode 100644 index 0000000000..2bd9e42196 --- /dev/null +++ b/packages/teams-js/test/private/externalAppCardActionsForCEA.spec.ts @@ -0,0 +1,226 @@ +import { errorLibraryNotInitialized } from '../../src/internal/constants'; +import { GlobalVars } from '../../src/internal/globalVars'; +import { ApiName } from '../../src/internal/telemetry'; +import { ExternalAppErrorCode } from '../../src/private/constants'; +import { externalAppCardActionsForCEA } from '../../src/private/externalAppCardActionsForCEA'; +import { ActionOpenUrlErrorCode, ActionOpenUrlType } from '../../src/private/interfaces'; +import { AppId, FrameContexts } from '../../src/public'; +import { app } from '../../src/public/app'; +import { errorNotSupportedOnPlatform } from '../../src/public/constants'; +import { Utils } from '../utils'; + +describe('externalAppCardActionsForCEA', () => { + let utils = new Utils(); + + // This ID was randomly generated for the purpose of these tests + const testAppId = new AppId('01b92759-b43a-4085-ac22-7772d94bb7a9'); + const testConversationId = '61f7f08d-477b-42b8-9c36-44eabb58eb92'; + + beforeEach(() => { + utils = new Utils(); + utils.mockWindow.parent = undefined; + utils.messages = []; + GlobalVars.isFramelessWindow = false; + }); + + afterEach(() => { + app._uninitialize(); + jest.clearAllMocks(); + }); + + describe('processActionSubmit', () => { + const allowedFrameContexts = [FrameContexts.content]; + const testActionSubmitPayload = { + id: 'testId', + data: {}, + }; + const testError = { + errorCode: ExternalAppErrorCode.INTERNAL_ERROR, + message: 'testMessage', + }; + + it('should not allow calls before initialization', async () => { + expect.assertions(1); + try { + await externalAppCardActionsForCEA.processActionSubmit(testAppId, testConversationId, testActionSubmitPayload); + } catch (e) { + expect(e).toEqual(new Error(errorLibraryNotInitialized)); + } + }); + + it('should throw error when externalAppCardActionsForCEA capability is not supported', async () => { + expect.assertions(1); + await utils.initializeWithContext(FrameContexts.content); + utils.setRuntimeConfig({ apiVersion: 2, supports: {} }); + try { + await externalAppCardActionsForCEA.processActionSubmit(testAppId, testConversationId, testActionSubmitPayload); + } catch (e) { + expect(e).toEqual(errorNotSupportedOnPlatform); + } + }); + + Object.values(FrameContexts).forEach((frameContext) => { + if (allowedFrameContexts.includes(frameContext)) { + it(`should resolve when called from context - ${frameContext}`, async () => { + expect.assertions(3); + await utils.initializeWithContext(frameContext); + utils.setRuntimeConfig({ apiVersion: 2, supports: { externalAppCardActionsForCEA: {} } }); + + const promise = externalAppCardActionsForCEA.processActionSubmit( + testAppId, + testConversationId, + testActionSubmitPayload, + ); + + const message = utils.findMessageByFunc(ApiName.ExternalAppCardActionsForCEA_ProcessActionSubmit); + if (message && message.args) { + expect(message).not.toBeNull(); + expect(message.args[0]).toEqual([testAppId, testConversationId, testActionSubmitPayload]); + utils.respondToMessage(message, undefined); + } + + await expect(promise).resolves.toBeUndefined(); + }); + + it(`should throw error from host when called from context - ${frameContext}`, async () => { + expect.assertions(3); + await utils.initializeWithContext(frameContext); + utils.setRuntimeConfig({ apiVersion: 2, supports: { externalAppCardActionsForCEA: {} } }); + const promise = externalAppCardActionsForCEA.processActionSubmit( + testAppId, + testConversationId, + testActionSubmitPayload, + ); + const message = utils.findMessageByFunc(ApiName.ExternalAppCardActionsForCEA_ProcessActionSubmit); + if (message && message.args) { + expect(message).not.toBeNull(); + expect(message.args[0]).toEqual([testAppId, testConversationId, testActionSubmitPayload]); + utils.respondToMessage(message, testError); + } + await expect(promise).rejects.toEqual(testError); + }); + } else { + it(`should not allow calls from ${frameContext} context`, async () => { + expect.assertions(1); + await utils.initializeWithContext(frameContext); + utils.setRuntimeConfig({ apiVersion: 2, supports: { externalAppCardActionsForCEA: {} } }); + + await expect( + externalAppCardActionsForCEA.processActionSubmit(testAppId, testConversationId, testActionSubmitPayload), + ).rejects.toThrowError( + new Error( + `This call is only allowed in following contexts: ${JSON.stringify( + allowedFrameContexts, + )}. Current context: "${frameContext}".`, + ), + ); + }); + } + }); + }); + + describe('processActionOpenUrl', () => { + const allowedFrameContexts = [FrameContexts.content]; + const testUrl = new URL('https://example.com'); + const testError = { + errorCode: ActionOpenUrlErrorCode.INTERNAL_ERROR, + message: 'testMessage', + }; + const testResponse = ActionOpenUrlType.DeepLinkDialog; + + it('should not allow calls before initialization', async () => { + expect.assertions(1); + try { + await externalAppCardActionsForCEA.processActionOpenUrl(testAppId, testConversationId, testUrl); + } catch (e) { + expect(e).toEqual(new Error(errorLibraryNotInitialized)); + } + }); + + it('should throw error when externalAppCardActionsForCEA capability is not supported', async () => { + expect.assertions(1); + await utils.initializeWithContext(FrameContexts.content); + utils.setRuntimeConfig({ apiVersion: 2, supports: {} }); + + try { + await externalAppCardActionsForCEA.processActionOpenUrl(testAppId, testConversationId, testUrl); + } catch (e) { + expect(e).toEqual(errorNotSupportedOnPlatform); + } + }); + + Object.values(FrameContexts).forEach((frameContext) => { + if (allowedFrameContexts.includes(frameContext)) { + it(`should resolve when called from context - ${frameContext}`, async () => { + expect.assertions(3); + await utils.initializeWithContext(frameContext); + utils.setRuntimeConfig({ apiVersion: 2, supports: { externalAppCardActionsForCEA: {} } }); + + const promise = externalAppCardActionsForCEA.processActionOpenUrl(testAppId, testConversationId, testUrl); + + const message = utils.findMessageByFunc(ApiName.ExternalAppCardActionsForCEA_ProcessActionOpenUrl); + if (message && message.args) { + expect(message).not.toBeNull(); + expect(message.args).toEqual([testAppId, testUrl.href, testConversationId]); + utils.respondToMessage(message, null, testResponse); + } + + await expect(promise).resolves.toEqual(testResponse); + }); + + it(`should throw error from host when called from context - ${frameContext}`, async () => { + expect.assertions(3); + await utils.initializeWithContext(frameContext); + utils.setRuntimeConfig({ apiVersion: 2, supports: { externalAppCardActionsForCEA: {} } }); + + const promise = externalAppCardActionsForCEA.processActionOpenUrl(testAppId, testConversationId, testUrl); + + const message = utils.findMessageByFunc(ApiName.ExternalAppCardActionsForCEA_ProcessActionOpenUrl); + if (message && message.args) { + expect(message).not.toBeNull(); + expect(message.args).toEqual([testAppId, testUrl.href, testConversationId]); + utils.respondToMessage(message, testError, null); + } + + await expect(promise).rejects.toEqual(testError); + }); + } else { + it(`should not allow calls from ${frameContext} context`, async () => { + expect.assertions(1); + await utils.initializeWithContext(frameContext); + utils.setRuntimeConfig({ apiVersion: 2, supports: { externalAppCardActionsForCEA: {} } }); + + await expect( + externalAppCardActionsForCEA.processActionOpenUrl(testAppId, testConversationId, testUrl), + ).rejects.toThrowError( + new Error( + `This call is only allowed in following contexts: ${JSON.stringify( + allowedFrameContexts, + )}. Current context: "${frameContext}".`, + ), + ); + }); + } + }); + }); + + describe('isSupported', () => { + it('should throw when library is not initialized', () => { + return expect(() => externalAppCardActionsForCEA.isSupported()).toThrowError( + new Error(errorLibraryNotInitialized), + ); + }); + it('should return true when externalAppCardActionsForCEA capability is supported', async () => { + expect.assertions(1); + await utils.initializeWithContext(FrameContexts.content); + utils.setRuntimeConfig({ apiVersion: 2, supports: { externalAppCardActionsForCEA: {} } }); + return expect(externalAppCardActionsForCEA.isSupported()).toEqual(true); + }); + it('should return false when externalAppCardActionsForCEA capability is not supported', async () => { + expect.assertions(1); + await utils.initializeWithContext(FrameContexts.content); + utils.setRuntimeConfig({ apiVersion: 2, supports: {} }); + return expect(externalAppCardActionsForCEA.isSupported()).toEqual(false); + }); + }); +});