Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
118 changes: 59 additions & 59 deletions __tests__/html2/activity/programmingLanguageTitle.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,58 +28,6 @@
import React from 'react';
import { createRoot } from 'react-dom/client';

const aiMessageEntity = Object.freeze({
'@context': 'https://schema.org',
'@id': '',
'@type': 'Message',
keywords: Object.freeze(['AIGeneratedContent']),
type: 'https://schema.org/Message'
});

// Programming languages are sent as-is in "programmingLanguage", often in lowercase or as an alias.
// Only JavaScript, Python, and TypeScript are highlighted by the bundled highlighter.
const COLLAPSIBLE_SAMPLES = Object.freeze([
Object.freeze({
isHighlighted: true,
programmingLanguage: 'javascript',
text: "console.log('Hello, World!');",
title: 'JavaScript'
}),
Object.freeze({
isHighlighted: true,
programmingLanguage: 'TS',
text: "const greeting: string = 'Hello, World!';",
title: 'TypeScript'
}),
Object.freeze({
isHighlighted: false,
programmingLanguage: 'c#',
text: 'Console.WriteLine("Hello, World!");',
title: 'C#'
}),
// The title of "sh" ("Shell") is overridden via "overrideLocalizedStrings" below.
Object.freeze({
isHighlighted: false,
programmingLanguage: 'sh',
text: 'echo "Hello, World!"',
title: 'Shell script'
}),
// Shell dialects keep their own name: only the first letter is capitalized.
Object.freeze({
isHighlighted: false,
programmingLanguage: 'bash',
text: "echo 'Hello, World!'",
title: 'Bash'
}),
// Unknown programming language: only the first letter is capitalized.
Object.freeze({
isHighlighted: false,
programmingLanguage: 'cobol',
text: 'DISPLAY "Hello, World!".',
title: 'Cobol'
})
]);

run(async function () {
const {
WebChat: { ReactWebChat, testIds }
Expand All @@ -99,8 +47,60 @@

await pageConditions.uiConnected();

const aiMessageEntity = Object.freeze({
'@context': 'https://schema.org',
'@id': '',
'@type': 'Message',
keywords: Object.freeze(['AIGeneratedContent']),
type: 'https://schema.org/Message'
});

// Programming languages are sent as-is in "programmingLanguage", often in lowercase or as an alias.
// Only JavaScript, Python, and TypeScript are highlighted by the bundled highlighter.
const collapsibleSamples = Object.freeze([
Object.freeze({
isHighlighted: true,
programmingLanguage: 'javascript',
text: "console.log('Hello, World!');",
title: 'JavaScript'
}),
Object.freeze({
isHighlighted: true,
programmingLanguage: 'TS',
text: "const greeting: string = 'Hello, World!';",
title: 'TypeScript'
}),
Object.freeze({
isHighlighted: false,
programmingLanguage: 'c#',
text: 'Console.WriteLine("Hello, World!");',
title: 'C#'
}),
// The title of "sh" ("Shell") is overridden via "overrideLocalizedStrings" above.
Object.freeze({
isHighlighted: false,
programmingLanguage: 'sh',
text: 'echo "Hello, World!"',
title: 'Shell script'
}),
// Shell dialects keep their own name: only the first letter is capitalized.
Object.freeze({
isHighlighted: false,
programmingLanguage: 'bash',
text: "echo 'Hello, World!'",
title: 'Bash'
}),
// Unknown programming language: only the first letter is capitalized.
Object.freeze({
isHighlighted: false,
programmingLanguage: 'cobol',
text: 'DISPLAY "Hello, World!".',
title: 'Cobol'
})
]);

// WHEN: The bot sends collapsible messages with code (emulated).
for (const { programmingLanguage, text } of COLLAPSIBLE_SAMPLES) {
for (const { programmingLanguage, text } of collapsibleSamples) {
await directLine.emulateIncomingActivity({
entities: [
{
Expand All @@ -116,7 +116,7 @@
});
}

await pageConditions.numActivitiesShown(COLLAPSIBLE_SAMPLES.length);
await pageConditions.numActivitiesShown(collapsibleSamples.length);

// WHEN: Expand every message.
for (const summary of document.querySelectorAll('summary.collapsible-content__summary')) {
Expand All @@ -129,17 +129,17 @@
await waitFor(() =>
expect(
Array.from(document.querySelectorAll('.code-block-content pre'), pre => pre.classList.contains('shiki'))
).toEqual(COLLAPSIBLE_SAMPLES.map(({ isHighlighted }) => isHighlighted))
).toEqual(collapsibleSamples.map(({ isHighlighted }) => isHighlighted))
);

await pageConditions.scrollStabilized();

// THEN: The code blocks should show the title of the programming language.
expect(
Array.from(document.querySelectorAll('.code-block-content__title'), ({ textContent }) => textContent)
).toEqual(COLLAPSIBLE_SAMPLES.map(({ title }) => title));
).toEqual(collapsibleSamples.map(({ title }) => title));

// THEN: Should match snapshot.
// THEN: The 6 expanded messages show their code under the titles JavaScript, TypeScript, C#, Shell script, Bash, and Cobol, only the first 2 highlighted.
await host.snapshot('local');

// WHEN: The bot sends a message with code in "py" (emulated).
Expand All @@ -155,7 +155,7 @@
type: 'message'
});

await pageConditions.numActivitiesShown(COLLAPSIBLE_SAMPLES.length + 1);
await pageConditions.numActivitiesShown(collapsibleSamples.length + 1);
await pageConditions.scrollStabilized();

// WHEN: Click the "View code" button.
Expand All @@ -168,7 +168,7 @@
'Code sample: Python'
);

// THEN: Should match snapshot.
// THEN: The "View code" dialog shows the Python code under the "Python" title.
await host.snapshot('local');
});
</script>
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 16 additions & 1 deletion __tests__/html2/carousel/flipperButton.focusVisible.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,12 @@
run(async function () {
const { directLine, store } = testHelpers.createDirectLineEmulator();
const variant = new URLSearchParams(location.search).get('variant');
const webChatElement = createElement(ReactWebChat, { directLine, store });
// The send box is not under test: hide it, including the send box of the Fluent theme.
const webChatElement = createElement(ReactWebChat, {
directLine,
sendBoxMiddleware: [() => () => () => false],
store
});

createRoot(document.getElementById('webchat')).render(
variant ? createElement(FluentThemeProvider, { variant }, webChatElement) : webChatElement
Expand Down Expand Up @@ -97,6 +102,14 @@
pageObjects.focusTranscript();
await host.sendKeys('ENTER');

// THEN: The first attachment should have the focus, both flippers should stay hidden.
await waitFor(() => expect(document.activeElement).toBe(pageElements.activityAttachments()[0][0]));
expect(getFlipperBodyOffset(nextFlipper)).toBe(50);
expect(previousFlipper.querySelector('.react-film__main__slider--hide')).toBeTruthy();

// THEN: The first card shows the focus indicator, no flipper is shown.
await host.snapshot('local');

// WHEN: Press SHIFT-TAB to move focus to the "Next" flipper.
await host.sendShiftTab();

Expand All @@ -106,6 +119,7 @@
await waitFor(() => expect(getFlipperBodyOffset(nextFlipper)).toBe(0));
expect(getFocusIndicatorStyle(nextFlipper)).toEqual(visibleFocusIndicatorStyle);

// THEN: The "Next" flipper shows at the right edge with a two-tone focus indicator, the first card has no focus indicator.
await host.snapshot('local');

// WHEN: Press SHIFT-TAB to move focus to the "Previous" flipper.
Expand All @@ -123,6 +137,7 @@
// THEN: The "Next" flipper should auto-hide after losing focus (1s delay, then 300ms slide out).
await waitFor(() => expect(getFlipperBodyOffset(nextFlipper)).toBe(50), { timeout: 2000 });

// THEN: The "Previous" flipper shows at the left edge with a two-tone focus indicator, the "Next" flipper is hidden.
await host.snapshot('local');
});
</script>
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<html>
<head>
<script>
location = './navigation.arrowKeys.formControls?variant=fluent';
location = './navigation.arrowKeys.formControls.cards?variant=fluent';
</script>
</head>
<body></body>
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
<!doctype html>
<html lang="en-US">
<head>
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
</head>
<body>
<main id="webchat"></main>
<script type="importmap">
{
"imports": {
"@testduet/wait-for": "https://esm.sh/@testduet/wait-for",
"botframework-webchat": "/__dist__/packages/bundle/static/botframework-webchat.js",
"botframework-webchat/": "/__dist__/packages/bundle/static/botframework-webchat/",
"botframework-webchat-fluent-theme": "/__dist__/packages/fluent-theme/static/botframework-webchat-fluent-theme.js",
"react": "/__dist__/packages/bundle/static/react.js",
"react-dom": "/__dist__/packages/bundle/static/react-dom.js",
"react-dom/client": "/__dist__/packages/bundle/static/react-dom/client.js"
}
}
</script>
<script type="module">
import '/test-harness.mjs';
import '/test-page-object.mjs';

import { waitFor } from '@testduet/wait-for';
import { createStoreWithOptions, ReactWebChat } from 'botframework-webchat';
import { FluentThemeProvider } from 'botframework-webchat-fluent-theme';
import { createElement } from 'react';
import { createRoot } from 'react-dom/client';

// TODO: This is for `createDirectLineEmulator` only, should find ways to eliminate this line.
window.WebChat = { createStoreWithOptions };

run(async function () {
const { directLine, store } = testHelpers.createDirectLineEmulator();
const variant = new URLSearchParams(location.search).get('variant');
// Hide the send box, it is not under test. The Fluent theme does not honor the `hideSendBox` style option.
const webChatElement = createElement(ReactWebChat, {
directLine,
sendBoxMiddleware: [() => () => () => false],
store
});

createRoot(document.getElementById('webchat')).render(
variant ? createElement(FluentThemeProvider, { variant }, webChatElement) : webChatElement
);

await pageConditions.uiConnected();

const createAdaptiveCard = index => ({
content: {
$schema: 'http://adaptivecards.io/schemas/adaptive-card.json',
actions: [{ title: `Add item ${index}`, type: 'Action.Submit' }],
body: [
{ size: 'large', text: `Item ${index}`, type: 'TextBlock', wrap: true },
{ id: `note-${index}`, label: `Note for item ${index}`, type: 'Input.Text', value: 'Hello' }
],
type: 'AdaptiveCard',
version: '1.5'
},
contentType: 'application/vnd.microsoft.card.adaptive'
});

// GIVEN: A carousel with Adaptive Card forms (text input and submit button) and a hero card with a button.
await directLine.emulateIncomingActivity({
attachmentLayout: 'carousel',
attachments: [
createAdaptiveCard(1),
{
content: {
buttons: [{ title: 'Buy item 2', type: 'imBack', value: 'Buy item 2' }],
text: 'Price: $XXX.XX USD',
title: 'Details about item 2'
},
contentType: 'application/vnd.microsoft.card.hero'
},
createAdaptiveCard(3)
],
from: { role: 'bot' },
type: 'message'
});

await pageConditions.numActivitiesShown(1);

// Wait for the live region to clear (1 s): its copy of the cards has text inputs without labels, which would fail the
// accessibility checks that run with every snapshot.
await waitFor(() => expect(pageElements.liveRegion().children).toHaveLength(0), { timeout: 5000 });

const carouselLayout = document.querySelector('.carousel-layout');
const carouselFilmstrip = carouselLayout.querySelector('.carousel-filmstrip');
const cards = Array.from(carouselFilmstrip.querySelectorAll('[aria-roledescription="attachment"]'));

const expectCarouselAtStart = async () => {
await pageConditions.carouselSettled(carouselLayout);

expect(carouselFilmstrip.scrollLeft).toBe(0);
expect(testHelpers.isCardInView(cards[0])).toBe(true);
};

// Focus should be on the card itself, not on the form controls inside it.
const expectFocusOnCard = async index => {
expect(document.activeElement).toBe(cards[index]);
expect(document.activeElement.matches(':focus-visible')).toBe(true);

await waitFor(() => expect(testHelpers.isCardInView(cards[index])).toBe(true));
await pageConditions.carouselSettled(carouselLayout);

expect(testHelpers.isCardInView(cards[index])).toBe(true);
};

expect(cards).toHaveLength(3);
expect(carouselFilmstrip.scrollLeft).toBe(0);

// WHEN: Focus on the first card via keyboard.
pageObjects.focusTranscript();
await host.sendKeys('ENTER');

// THEN: The first card has focus, the carousel is at the start.
await expectFocusOnCard(0);
await expectCarouselAtStart();
await host.snapshot('local');

// WHEN: Press RIGHT.
await host.sendKeys('ARROW_RIGHT');

// THEN: The hero card has focus (not its button) and is scrolled to the center.
await expectFocusOnCard(1);
await host.snapshot('local');

// WHEN: Press RIGHT again.
await host.sendKeys('ARROW_RIGHT');

// THEN: The third card has focus (not its form controls) and the carousel is scrolled to the end.
await expectFocusOnCard(2);
await host.snapshot('local');

// WHEN: Press LEFT.
await host.sendKeys('ARROW_LEFT');

// THEN: The hero card has focus again and is scrolled to the center.
await expectFocusOnCard(1);
await host.snapshot('local');

// WHEN: Press LEFT again.
await host.sendKeys('ARROW_LEFT');

// THEN: The first card has focus again, the carousel is back at the start.
await expectFocusOnCard(0);
await expectCarouselAtStart();
await host.snapshot('local');
});
</script>
</body>
</html>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Diff not rendered.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!doctype html>
<html>
<head>
<script>
location = './navigation.arrowKeys.formControls.heroCardButton?variant=fluent';
</script>
</head>
<body></body>
</html>
Loading
Loading