Skip to content

tests/message-extensions & other improvements #213

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jun 18, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions packages/api/src/models/card/card-action.ts
Original file line number Diff line number Diff line change
@@ -7,13 +7,14 @@ export type CardActionType =
| 'showImage'
| 'downloadFile'
| 'signin'
| 'call';
| 'call'
| 'invoke';

export type CardAction = {
/**
* The type of action implemented by this button. Possible values include: 'openUrl', 'imBack',
* 'postBack', 'playAudio', 'playVideo', 'showImage', 'downloadFile', 'signin', 'call',
* messageBack', 'openApp'
* messageBack', 'openApp', 'invoke'
*/
type: CardActionType;

12 changes: 11 additions & 1 deletion tests/message-extensions/src/card.ts
Original file line number Diff line number Diff line change
@@ -161,7 +161,17 @@ export async function createDummyCards(searchQuery: string) {
thumbnail: {
title: item.title,
text: item.description,
} as ThumbnailCard,
// When a user clicks on a list item in Teams:
// - If the thumbnail has a `tap` property: Teams will trigger the `message.ext.select-item` activity
// - If no `tap` property: Teams will insert the full adaptive card into the compose box
// tap: {
// type: "invoke",
// title: item.title,
// value: {
// "option": index,
// },
// },
} satisfies ThumbnailCard,
};
});

56 changes: 56 additions & 0 deletions tests/message-extensions/src/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<!-- :snippet-start: message-ext-settings-page -->
<html>
<body>
<form>
<fieldset>
<legend>What programming language do you prefer?</legend>
<input type="radio" name="selectedOption" value="typescript" />Typescript<br />
<input type="radio" name="selectedOption" value="csharp" />C#<br />
</fieldset>

<br />
<input type="button" onclick="onSubmit()" value="Save" /> <br />
</form>

<script src="https://res.cdn.office.net/teams-js/2.34.0/js/MicrosoftTeams.min.js" integrity="sha384-brW9AazbKR2dYw2DucGgWCCcmrm2oBFV4HQidyuyZRI/TnAkmOOnTARSTdps3Hwt" crossorigin="anonymous"></script>

<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function () {
// Get the selected option from the URL
var urlParams = new URLSearchParams(window.location.search);
var selectedOption = urlParams.get("selectedOption");
if (selectedOption) {
var checkboxes = document.getElementsByName("selectedOption");
for (var i = 0; i < checkboxes.length; i++) {
var thisCheckbox = checkboxes[i];
if (selectedOption.includes(thisCheckbox.value)) {
checkboxes[i].checked = true;
}
}
}
});
</script>

<script type="text/javascript">
// initialize the Teams JS SDK
microsoftTeams.app.initialize();

// Run when the user clicks the submit button
function onSubmit() {
var newSettings = "";

var checkboxes = document.getElementsByName("selectedOption");

for (var i = 0; i < checkboxes.length; i++) {
if (checkboxes[i].checked) {
newSettings = checkboxes[i].value;
}
}

// Closes the settings page and returns the selected option to the bot
microsoftTeams.authentication.notifySuccess(newSettings);
}
</script>
</body>
</html>
<!-- :snippet-end: message-ext-settings-page -->
65 changes: 65 additions & 0 deletions tests/message-extensions/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import path from 'path';

import { cardAttachment } from '@microsoft/teams.api';
import { App } from '@microsoft/teams.apps';
import { IAdaptiveCard } from '@microsoft/teams.cards';
@@ -11,6 +13,7 @@ import {
createLinkUnfurlCard,
createMessageDetailsCard,
} from './card';

const app = new App({
logger: new ConsoleLogger('@tests/message-extensions', { level: 'debug' }),
plugins: [new DevtoolsPlugin()],
@@ -129,6 +132,68 @@ app.on('message.ext.query', async ({ activity }) => {
});
// :snippet-end: message-ext-query

// :snippet-start: message-ext-select-item
app.on('message.ext.select-item', async ({ activity, send }) => {
const { option } = activity.value;

await send(`Selected item: ${option}`);

return {
status: 200,
};
});
// :snippet-end: message-ext-select-item

// :snippet-start: message-ext-query-settings-url
app.on('message.ext.query-settings-url', async ({ activity }) => {
// Get user settings from storage if available
const userSettings = await app.storage.get(activity.from.id) || { selectedOption: '' };
const escapedSelectedOption = encodeURIComponent(userSettings.selectedOption);

return {
composeExtension: {
type: 'config',
suggestedActions: {
actions: [
{
type: 'openUrl',
title: 'Settings',
// ensure the bot endpoint is set in the environment variables
// process.env.BOT_ENDPOINT is not populated by default in the Teams Toolkit setup.
value: `${process.env.BOT_ENDPOINT}/tabs/settings?selectedOption=${escapedSelectedOption}`
}
]
}
}
};
});
// :snippet-end: message-ext-query-settings-url

// :snippet-start: message-ext-setting
app.on('message.ext.setting', async ({ activity, send }) => {
const { state } = activity.value;
if (state == 'CancelledByUser') {
return {
status: 400
};
}
const selectedOption = state;

// Save the selected option to storage
await app.storage.set(activity.from.id, { selectedOption });

await send(`Selected option: ${selectedOption}`);

return {
status: 200
};
});
// :snippet-end: message-ext-setting

// :snippet-start: message-ext-serve-html
app.tab('settings', path.resolve(__dirname));
// :snippet-end: message-ext-serve-html

(async () => {
await app.start();
})();