diff --git a/apps/teams-test-app/e2e-test-data/otherAppStateChange.json b/apps/teams-test-app/e2e-test-data/otherAppStateChange.json index 73f978b966..d52f39f24a 100644 --- a/apps/teams-test-app/e2e-test-data/otherAppStateChange.json +++ b/apps/teams-test-app/e2e-test-data/otherAppStateChange.json @@ -23,10 +23,9 @@ "type": "registerAndRaiseEvent", "boxSelector": "#box_otherAppStateChange_registerInstallHandler", "eventName": "otherApp.install", - "eventData": - { - "appIds": ["123", "456"] - }, + "eventData": { + "appIds": ["123", "456"] + }, "expectedTestAppValue": "received" }, { @@ -34,6 +33,12 @@ "type": "callResponse", "boxSelector": "#box_otherAppStateChange_unregisterInstallHandler", "expectedTestAppValue": "received" + }, + { + "title": "notifyInstallCompleted - Success", + "type": "callResponse", + "boxSelector": "#box_otherAppStateChange_notifyInstallCompleted", + "expectedTestAppValue": "notified" } ] } diff --git a/apps/teams-test-app/e2e-test-data/store.json b/apps/teams-test-app/e2e-test-data/store.json new file mode 100644 index 0000000000..0c11eabd94 --- /dev/null +++ b/apps/teams-test-app/e2e-test-data/store.json @@ -0,0 +1,165 @@ +{ + "name": "Store", + "platforms": "Web", + "checkIsSupported": { + "domElementName": "checkCapabilityStore" + }, + "version": ">2.31.0", + "hostSdkVersion": { + "web": ">4.5.0" + }, + "testCases": [ + { + "title": "openStoreExperience API Call Full Store - Success", + "type": "callResponse", + "boxSelector": "#box_storeOpen", + "inputValue": { + "dialogType": "fullstore", + "size": { + "width": "large", + "height": "large" + } + }, + "expectedAlertValue": "openStoreExperience called with ##JSON_INPUT_VALUE##" + }, + { + "title": "openStoreExperience API Call App Detail - Success", + "type": "callResponse", + "boxSelector": "#box_storeOpen", + "inputValue": { + "dialogType": "appdetail", + "appId": "1542629c-01b3-4a6d-8f76-1938b779e48d" + }, + "expectedAlertValue": "openStoreExperience called with ##JSON_INPUT_VALUE##" + }, + { + "title": "openStoreExperience API Call App Detail With Invalid AppId - Fail", + "type": "callResponse", + "boxSelector": "#box_storeOpen", + "inputValue": { + "dialogType": "appdetail", + "appId": "123" + }, + "expectedTestAppValue": "Error: Error: Potential app id (123) is invalid; its length 3 is not within the length limits (4-256)." + }, + { + "title": "openStoreExperience API Call App Detail With Empty AppId - Fail", + "type": "callResponse", + "boxSelector": "#box_storeOpen", + "inputValue": { + "dialogType": "appdetail", + "appId": "" + }, + "expectedTestAppValue": "Error: Error: Potential app id () is invalid; its length 0 is not within the length limits (4-256)." + }, + { + "title": "openStoreExperience API Call App Detail Without AppId - Fail", + "type": "callResponse", + "boxSelector": "#box_storeOpen", + "inputValue": { + "dialogType": "appdetail" + }, + "expectedTestAppValue": "Error: Error: No App Id present, but AppId needed to open AppDetail store" + }, + { + "title": "openStoreExperience API Call Specific Store - Success", + "type": "callResponse", + "boxSelector": "#box_storeOpen", + "inputValue": { + "dialogType": "specificstore", + "collectionId": "copilotplugins" + }, + "expectedAlertValue": "openStoreExperience called with ##JSON_INPUT_VALUE##" + }, + { + "title": "openStoreExperience API Call Specific Store With Empty CollectionId - Fail", + "type": "callResponse", + "boxSelector": "#box_storeOpen", + "inputValue": { + "dialogType": "specificstore", + "collectionId": "" + }, + "expectedTestAppValue": "Error: Error: No Collection Id present, but CollectionId needed to open a store specific to a collection" + }, + { + "title": "openStoreExperience API Call Specific Store Without CollectionId - Fail", + "type": "callResponse", + "boxSelector": "#box_storeOpen", + "inputValue": { + "dialogType": "specificstore" + }, + "expectedTestAppValue": "Error: Error: No Collection Id present, but CollectionId needed to open a store specific to a collection" + }, + { + "title": "openStoreExperience API Call With Invalid Dialog Type - Fail", + "type": "callResponse", + "boxSelector": "#box_storeOpen", + "inputValue": { + "dialogType": "123" + }, + "expectedTestAppValue": "Error: Error: Invalid store dialog type, but type needed to specify store to open" + }, + { + "title": "openStoreExperience API Call Without Permission - Fail", + "type": "callResponse", + "boxSelector": "#box_storeOpen", + "inputValue": { + "dialogType": "fullstore" + }, + "testUrlParams": [["appDefOverrides", "{\"isFullTrustApp\": false, \"isMicrosoftOwned\": false}"]], + "expectedTestAppValue": "Error: Error: 500, message: App does not have the required permissions for this operation" + }, + { + "title": "openStoreExperience API Call With Invalid Dialog Width Size - Fail", + "type": "callResponse", + "boxSelector": "#box_storeOpen", + "inputValue": { + "dialogType": "fullstore", + "size": { + "width": -300, + "height": "large" + } + }, + "expectedTestAppValue": "Error: Error: Invalid store dialog size" + }, + { + "title": "openStoreExperience API Call With Invalid Dialog Height Size - Fail", + "type": "callResponse", + "boxSelector": "#box_storeOpen", + "inputValue": { + "dialogType": "fullstore", + "size": { + "width": 300, + "height": -300 + } + }, + "expectedTestAppValue": "Error: Error: Invalid store dialog size" + }, + { + "title": "openStoreExperience API Call In-Context-Store with Default Filters - Success", + "type": "callResponse", + "boxSelector": "#box_storeOpen", + "inputValue": { + "dialogType": "ics" + }, + "expectedAlertValue": "openStoreExperience called with {\"dialogType\":\"ics\",\"appCapability\":\"Tab\",\"installationScope\":\"Personal\"}" + }, + { + "title": "openStoreExperience API Call In-Context-Store with Customized Filters - Success", + "type": "callResponse", + "boxSelector": "#box_storeOpen", + "inputValue": { + "dialogType": "ics", + "appCapability": "Bot", + "appMetaCapabilities": ["copilotplugins", "copilotExtensions"], + "installationScope": "Team", + "filteredOutAppIds": ["123"], + "size": { + "width": "large", + "height": "large" + } + }, + "expectedAlertValue": "openStoreExperience called with ##JSON_INPUT_VALUE##" + } + ] +} diff --git a/apps/teams-test-app/src/components/StoreApis.tsx b/apps/teams-test-app/src/components/StoreApis.tsx index 744d22807b..d1ed10f021 100644 --- a/apps/teams-test-app/src/components/StoreApis.tsx +++ b/apps/teams-test-app/src/components/StoreApis.tsx @@ -1,4 +1,4 @@ -import { AppId, store } from '@microsoft/teams-js'; +import { AppId, DialogSize, store } from '@microsoft/teams-js'; import { ReactElement } from 'react'; import React from 'react'; @@ -20,7 +20,16 @@ const StoreAPIs = (): ReactElement => { }); const OpenStore = (): ReactElement => - ApiWithTextInput<{ dialogType: string; appId?: string; collectionId?: string }>({ + ApiWithTextInput<{ + dialogType: string; + appId?: string; + collectionId?: string; + size?: DialogSize; + appCapability?: string; + appMetaCapabilities?: string[]; + installationScope?: string; + filteredOutAppIds?: string[]; + }>({ name: 'storeOpen', title: 'Store Open', onClick: { @@ -35,6 +44,11 @@ const StoreAPIs = (): ReactElement => { dialogType: input.dialogType, appId: appId, collectionId: input.collectionId, + size: input.size, + appCapability: input.appCapability, + appMetaCapabilities: input.appMetaCapabilities, + installationScope: input.installationScope, + filteredOutAppIds: input.filteredOutAppIds, }; // eslint-disable-next-line no-useless-catch try { @@ -48,6 +62,10 @@ const StoreAPIs = (): ReactElement => { defaultInput: JSON.stringify({ dialogType: 'appdetail', appId: '1542629c-01b3-4a6d-8f76-1938b779e48d', + size: { + width: 'large', + height: 300, + }, }), }); return ( diff --git a/change/@microsoft-teams-js-dda2304f-2384-4778-83a4-caa10d929abf.json b/change/@microsoft-teams-js-dda2304f-2384-4778-83a4-caa10d929abf.json new file mode 100644 index 0000000000..d55d68da1e --- /dev/null +++ b/change/@microsoft-teams-js-dda2304f-2384-4778-83a4-caa10d929abf.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Added the ability to specify size of store dialogs and ability to pass filter info for ics.", + "packageName": "@microsoft/teams-js", + "email": "yt6520143@163.com", + "dependentChangeType": "patch" +} diff --git a/packages/teams-js/src/private/store.ts b/packages/teams-js/src/private/store.ts index e6770c8239..269692079f 100644 --- a/packages/teams-js/src/private/store.ts +++ b/packages/teams-js/src/private/store.ts @@ -1,6 +1,7 @@ import { callFunctionInHost } from '../internal/communication'; import { ensureInitialized } from '../internal/internalAPIs'; import { ApiName, ApiVersionNumber, getApiVersionTag } from '../internal/telemetry'; +import { DialogSize } from '../public'; import { AppId } from '../public/appId'; import { errorNotSupportedOnPlatform, FrameContexts } from '../public/constants'; import { runtime } from '../public/runtime'; @@ -42,15 +43,64 @@ export enum StoreDialogType { /** * @beta * @hidden - * Interface of open full store, copilot store and in-context-store function parameter + * Interface of store dialog size * @internal * Limited to Microsoft-internal use */ -export interface OpenFullStoreAndICSParams { +export interface StoreSizeInfo { /** - * the store dialog type, defined by {@link StoreDialogType} + * the store dialog size, defined by {@link DialogSize}, if not present, the host will choose an appropriate size */ - dialogType: StoreDialogType.FullStore | StoreDialogType.InContextStore; + size?: DialogSize; +} + +/** + * @beta + * @hidden + * Interface for opening the full store function parameters + * @internal + * Limited to Microsoft-internal use + */ +export interface OpenFullStoreParams extends StoreSizeInfo { + /** + * The store dialog type, specifically the full store, defined by {@link StoreDialogType} + */ + dialogType: StoreDialogType.FullStore; +} +/** + * @beta + * @hidden + * Interface for opening the in-context store function parameters + * @internal + * Limited to Microsoft-internal use + */ +export interface OpenInContextStoreParams extends StoreSizeInfo { + /** + * The store dialog type, specifically the in-context store, defined by {@link StoreDialogType} + */ + dialogType: StoreDialogType.InContextStore; + + /** + * The application capability (e.g., "Tab", "Bot", "Messaging", "Connector", "CUSTOMBOT"). + * Defaults to "Tab". + */ + appCapability?: string; + + /** + * The application meta capabilities (e.g., ["copilotPlugins", "copilotExtensions"]). + */ + appMetaCapabilities?: string[]; + + /** + * The installation scope (e.g., "Personal" | "Team"). + * Defaults to "Personal". + */ + installationScope?: string; + + /** + * A list of app IDs to be filtered out. + */ + filteredOutAppIds?: string[]; } /** * @beta @@ -59,7 +109,7 @@ export interface OpenFullStoreAndICSParams { * @internal * Limited to Microsoft-internal use */ -export interface OpenAppDetailParams { +export interface OpenAppDetailParams extends StoreSizeInfo { /** * need to be app detail type, defined by {@link StoreDialogType} */ @@ -76,7 +126,7 @@ export interface OpenAppDetailParams { * @internal * Limited to Microsoft-internal use */ -export interface OpenSpecificStoreParams { +export interface OpenSpecificStoreParams extends StoreSizeInfo { /** * need to be specific store type, defined by {@link StoreDialogType} */ @@ -89,11 +139,28 @@ export interface OpenSpecificStoreParams { /** * @beta * @hidden - * Interface of open store function parameters, including OpenFullStoreAndICSParams, OpenAppDetailParams, OpenSpecificStoreParams + * Interface of open store function parameters, including: + * - `OpenAppDetailParams` + * - `OpenFullStoreParams` + * - `OpenInContextStoreParams` + * - `OpenSpecificStoreParams` * @internal * Limited to Microsoft-internal use */ -export type OpenStoreParams = OpenFullStoreAndICSParams | OpenAppDetailParams | OpenSpecificStoreParams; +export type OpenStoreParams = + | OpenFullStoreParams + | OpenInContextStoreParams + | OpenAppDetailParams + | OpenSpecificStoreParams; + +/** + * @beta + * @hidden + * error message when getting illegal store dialog size + * @internal + * Limited to Microsoft-internal use + */ +export const errorInvalidDialogSize = 'Invalid store dialog size'; /** * @beta @@ -135,21 +202,42 @@ export async function openStoreExperience(openStoreParams: OpenStoreParams): Pro if (!isSupported()) { throw errorNotSupportedOnPlatform; } - if (openStoreParams === undefined || !Object.values(StoreDialogType).includes(openStoreParams.dialogType)) { + const { dialogType, size } = openStoreParams; + if (openStoreParams === undefined || !Object.values(StoreDialogType).includes(dialogType)) { throw new Error(errorInvalidDialogType); } - if (openStoreParams.dialogType === StoreDialogType.AppDetail && !(openStoreParams.appId instanceof AppId)) { + if (dialogType === StoreDialogType.AppDetail && !(openStoreParams.appId instanceof AppId)) { throw new Error(errorMissingAppId); } - if (openStoreParams.dialogType === StoreDialogType.SpecificStore && !openStoreParams.collectionId) { + if (dialogType === StoreDialogType.SpecificStore && !openStoreParams.collectionId) { throw new Error(errorMissingCollectionId); } + if (size !== undefined) { + const { width, height } = size; + if (width !== undefined && typeof width === 'number' && width < 0) { + throw new Error(errorInvalidDialogSize); + } + if (height !== undefined && typeof height === 'number' && height < 0) { + throw new Error(errorInvalidDialogSize); + } + } + const inContextStoreFilters = + dialogType === StoreDialogType.InContextStore + ? JSON.stringify({ + appCapability: openStoreParams.appCapability ?? 'Tab', + appMetaCapabilities: openStoreParams.appMetaCapabilities, + installationScope: openStoreParams.installationScope ?? 'Personal', + filteredOutAppIds: openStoreParams.filteredOutAppIds, + }) + : undefined; return callFunctionInHost( ApiName.Store_Open, [ openStoreParams.dialogType, (openStoreParams as OpenAppDetailParams).appId, (openStoreParams as OpenSpecificStoreParams).collectionId, + JSON.stringify(openStoreParams.size), + inContextStoreFilters, ], getApiVersionTag(StoreVersionTagNum, ApiName.Store_Open), ); diff --git a/packages/teams-js/test/private/store.spec.ts b/packages/teams-js/test/private/store.spec.ts index b418b5796e..6293c48614 100644 --- a/packages/teams-js/test/private/store.spec.ts +++ b/packages/teams-js/test/private/store.spec.ts @@ -1,7 +1,7 @@ import { errorLibraryNotInitialized } from '../../src/internal/constants'; import { store } from '../../src/private'; import { app, AppId } from '../../src/public'; -import { errorNotSupportedOnPlatform, FrameContexts } from '../../src/public/constants'; +import { DialogDimension, errorNotSupportedOnPlatform, FrameContexts } from '../../src/public/constants'; import { _minRuntimeConfigToUninitialize, latestRuntimeApiVersion } from '../../src/public/runtime'; import { Utils } from '../utils'; @@ -23,7 +23,7 @@ describe('store', () => { }); describe('openStoreExperience', () => { - const paramFullStore: store.OpenFullStoreAndICSParams = { + const paramFullStore: store.OpenFullStoreParams = { dialogType: store.StoreDialogType.FullStore, }; const paramAppDetail: store.OpenAppDetailParams = { @@ -31,6 +31,27 @@ describe('store', () => { appId: new AppId('1542629c-01b3-4a6d-8f76-1938b779e48d'), }; const argsAppDetail = ['appdetail', '1542629c-01b3-4a6d-8f76-1938b779e48d']; + const paramAppDetailWithSize: store.OpenAppDetailParams = { + dialogType: store.StoreDialogType.AppDetail, + appId: new AppId('1542629c-01b3-4a6d-8f76-1938b779e48d'), + size: { + width: DialogDimension.Large, + height: 300, + }, + }; + const paramAppDetailWithInvalidSize: store.OpenAppDetailParams = { + dialogType: store.StoreDialogType.AppDetail, + appId: new AppId('1542629c-01b3-4a6d-8f76-1938b779e48d'), + size: { + width: DialogDimension.Large, + height: -300, + }, + }; + const argsAppDetailWithSize = [ + 'appdetail', + '1542629c-01b3-4a6d-8f76-1938b779e48d', + JSON.stringify(paramAppDetail.size), + ]; const paramInvalidStoreType = { dialogType: '123', }; @@ -69,6 +90,16 @@ describe('store', () => { }); }); + it(`should pass along entire openStoreExperience parameter in ${context} context`, async () => { + await utils.initializeWithContext(context); + utils.setRuntimeConfig({ apiVersion: latestRuntimeApiVersion, supports: { store: {} } }); + store.openStoreExperience(paramAppDetailWithSize).then(() => { + const openMessage = utils.findMessageByFunc('store.open'); + expect(openMessage).not.toBeNull(); + expect(openMessage?.args).toEqual([argsAppDetailWithSize]); + }); + }); + it(`should throw error when trying to open store but getting invalid store type in ${context} context`, async () => { await utils.initializeWithContext(context); utils.setRuntimeConfig({ apiVersion: latestRuntimeApiVersion, supports: { store: {} } }); @@ -95,6 +126,14 @@ describe('store', () => { expect(e).toEqual(new Error(store.errorMissingAppId)); }); }); + + it(`should throw error when trying to open app details but with invalid dialog size in ${context} context`, async () => { + await utils.initializeWithContext(context); + utils.setRuntimeConfig({ apiVersion: latestRuntimeApiVersion, supports: { store: {} } }); + store.openStoreExperience(paramAppDetailWithInvalidSize as store.OpenAppDetailParams).catch((e) => { + expect(e).toEqual(new Error(store.errorInvalidDialogSize)); + }); + }); } }); });