Skip to content
Merged
Show file tree
Hide file tree
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
95 changes: 95 additions & 0 deletions __tests__/html2/hooks/useStyles/cssModuleTheme.fluent.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<!doctype html>
<html lang="en-US">
<head>
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
<script crossorigin="anonymous" src="https://unpkg.com/@babel/standalone@7.8.7/babel.min.js"></script>
<script crossorigin="anonymous" src="https://unpkg.com/react@16.8.6/umd/react.production.min.js"></script>
<script crossorigin="anonymous" src="https://unpkg.com/react-dom@16.8.6/umd/react-dom.production.min.js"></script>
<script crossorigin="anonymous" src="/test-harness.js"></script>
<script crossorigin="anonymous" src="/test-page-object.js"></script>
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
<script crossorigin="anonymous" src="/__dist__/botframework-webchat-fluent-theme.production.min.js"></script>
<style>
.test-theme__adaptive-card-renderer {
outline: dashed 2px Blue;
outline-offset: -8px; /* Inside the bubble outline */
}

.test-theme__bubble {
outline: dashed 2px Red;
outline-offset: -2px;
}

.test-theme__sendbox {
outline: dashed 2px Green;
outline-offset: -2px;
}
</style>
</head>
<body>
<main id="webchat"></main>
<script type="text/babel">
run(async function () {
const {
React,
ReactDOM: { render },
WebChat: {
FluentThemeProvider,
internal: { CSSModuleThemeComposer },
ReactWebChat
}
} = window; // Imports in UMD fashion.

const { directLine, store } = testHelpers.createDirectLineEmulator();

// GIVEN: A theme with local names from 3 packages, provided above Fluent.
const theme = Object.freeze({
'adaptive-card-renderer': 'test-theme__adaptive-card-renderer', // AdaptiveCardRenderer.module.css, botframework-webchat
bubble: 'test-theme__bubble', // Bubble.module.css, botframework-webchat-component
sendbox: 'test-theme__sendbox' // SendBox.module.css, botframework-webchat-fluent-theme
});

render(
<CSSModuleThemeComposer theme={theme}>
<FluentThemeProvider>
<ReactWebChat directLine={directLine} store={store} />
</FluentThemeProvider>
</CSSModuleThemeComposer>,
document.getElementById('webchat')
);

await pageConditions.uiConnected();

// WHEN: A message with an Adaptive Card is received.
await directLine.emulateIncomingActivity({
attachments: [
{
content: {
body: [{ text: 'Adaptive Card', type: 'TextBlock' }],
type: 'AdaptiveCard',
version: '1.2'
},
contentType: 'application/vnd.microsoft.card.adaptive'
}
],
text: 'Message with an Adaptive Card',
type: 'message'
});

await pageConditions.numActivitiesShown(1);

// THEN: Every element of these local names should have the theme class, in each of the 3 packages.
for (const [localName, className] of Object.entries(theme)) {
const elements = Array.from(document.getElementsByClassName(localName));

expect(elements.length).toBeGreaterThan(0);
expect(elements.every(element => element.classList.contains(className))).toBe(true);
expect(document.getElementsByClassName(className)).toHaveLength(elements.length);
}

// THEN: The bubbles (red), the Adaptive Card inside its bubble (blue) and the send box (green) should have dashed outlines.
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.
3 changes: 3 additions & 0 deletions packages/bundle/.eslintrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@ rules:
- group:
- '@msinternal/botframework-webchat-api-*'
message: 'To prevent duplicated import, @msinternal/botframework-webchat-api-* is only allowed to import in botframework-webchat-api.'
paths:
- name: '@msinternal/botframework-webchat-styles/react'
message: 'Every copy of @msinternal/botframework-webchat-styles/react has its own React context, import it from botframework-webchat-component/internal.js instead.'
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
/* eslint no-magic-numbers: ["error", { "ignore": [-1, 0, 2] }] */

import { validateProps } from '@msinternal/botframework-webchat-react-valibot';
import { useStyles } from '@msinternal/botframework-webchat-styles/react';
import { type Action as AdaptiveCardAction, type OpenUrlAction, type SubmitAction } from 'adaptivecards';
import { ErrorBoxPolymiddlewareProxy } from 'botframework-webchat-api/middleware.js';
import { getTabIndex, hooks } from 'botframework-webchat-component';
import { useQueueStaticElement } from 'botframework-webchat-component/internal.js';
import { useQueueStaticElement, useStyles } from 'botframework-webchat-component/internal.js';
import { type DirectLineCardAction } from 'botframework-webchat-core';
import cx from 'classnames';
import React, {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { validateProps, type InferReactProps } from '@msinternal/botframework-webchat-react-valibot';
import { useStyles } from '@msinternal/botframework-webchat-styles/react';
import { hooks } from 'botframework-webchat-component';
import { ActivityButton, FixedWidthImage } from 'botframework-webchat-component/internal.js';
import { ActivityButton, FixedWidthImage, useStyles } from 'botframework-webchat-component/internal.js';
import cx from 'classnames';
import React, { memo, useCallback, useRef, useState, type SyntheticEvent } from 'react';
import { boolean, object, optional, pipe, readonly, string } from 'valibot';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/* eslint react/no-array-index-key: "off" */

import { validateProps, type InferReactProps } from '@msinternal/botframework-webchat-react-valibot';
import { useStyles } from '@msinternal/botframework-webchat-styles/react';
import { Components } from 'botframework-webchat-component';
import { useStyles } from 'botframework-webchat-component/internal.js';
import { onErrorResumeNext } from 'botframework-webchat-core';
import React, { memo } from 'react';
import { boolean, object, optional, pipe, readonly, string } from 'valibot';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/* eslint react/no-array-index-key: "off" */

import { validateProps, type InferReactProps } from '@msinternal/botframework-webchat-react-valibot';
import { useStyles } from '@msinternal/botframework-webchat-styles/react';
import { Components } from 'botframework-webchat-component';
import { useStyles } from 'botframework-webchat-component/internal.js';
import React, { memo } from 'react';
import { boolean, object, optional, pipe, readonly, string } from 'valibot';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { validateProps, type InferReactProps } from '@msinternal/botframework-webchat-react-valibot';
import { useStyles } from '@msinternal/botframework-webchat-styles/react';
import { useStyles } from 'botframework-webchat-component/internal.js';
import React, { memo } from 'react';
import { boolean, object, optional, pipe, readonly, string } from 'valibot';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/* eslint react/no-array-index-key: "off" */

import type { InferReactProps } from '@msinternal/botframework-webchat-react-valibot';
import { useStyles } from '@msinternal/botframework-webchat-styles/react';
import { Components } from 'botframework-webchat-component';
import { useStyles } from 'botframework-webchat-component/internal.js';
import React, { memo } from 'react';
import { boolean, object, optional, parse, pipe, readonly, string } from 'valibot';

Expand Down
5 changes: 4 additions & 1 deletion packages/bundle/src/boot/actual/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
export { usePostVoiceActivity, useShouldShowMicrophoneButton } from 'botframework-webchat-api/internal.js';
export {
CodeHighlighterComposer,
combineCSSModules,
createIconComponent,
CSSModuleThemeComposer,
InjectStyleElements,
injectStyleElementsPropsSchema,
parseDocumentFragmentFromString,
Expand All @@ -11,7 +13,8 @@ export {
serializeDocumentFragmentIntoString,
useActivityStyleOptions,
useCodeHighlighter,
useLiveRegion
useLiveRegion,
useStyles
} from 'botframework-webchat-component/internal.js';
export {
getOrgSchemaMessage,
Expand Down
10 changes: 9 additions & 1 deletion packages/component/src/boot/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@ export {
type BetterLinkDocumentModAttributeSetter,
type BetterLinkDocumentModDecoration
} from '@msinternal/botframework-webchat-component-better-link';
export { InjectStyleElements, injectStyleElementsPropsSchema } from '@msinternal/botframework-webchat-styles/react';
// The styles package is bundled into every package that imports it, and each copy has its own React context.
// Other packages must use this copy, so a theme above Web Chat reaches every useStyles().
export {
combineCSSModules,
CSSModuleThemeComposer,
InjectStyleElements,
injectStyleElementsPropsSchema,
useStyles
} from '@msinternal/botframework-webchat-styles/react';
export { default as ActivityButton } from '../Attachment/Text/private/ActivityButton';
export {
CodeHighlighterComposer,
Expand Down
5 changes: 5 additions & 0 deletions packages/fluent-theme/.eslintrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,10 @@ env:
browser: true

rules:
no-restricted-imports:
- error
- paths:
- name: '@msinternal/botframework-webchat-styles/react'
message: 'Every copy of @msinternal/botframework-webchat-styles/react has its own React context, import it from botframework-webchat/internal.js instead.'
react/destructuring-assignment: off
react/require-default-props: off
84 changes: 84 additions & 0 deletions packages/fluent-theme/src/__tests__/sharedStylesRuntime.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/** @jest-environment @happy-dom/jest-environment */

// Every copy of the styles runtime has its own CSSModuleThemeContext: a theme provided through one copy is invisible to
// useStyles() of another copy. Web Chat must ship one copy, from botframework-webchat-component.

const { readdirSync, readFileSync } = require('fs');
const { dirname, join } = require('path');

// Only the styles runtime names its context, minifiers keep string literals.
const STYLES_RUNTIME_MARKER = 'WebChat.CSSModuleThemeContext';

// Lists the build outputs that bundle the styles runtime, per format, from esbuild metafiles (tsup `metafile: true`).
function outputsWithStylesRuntime(packageName) {
const distPath = dirname(require.resolve(packageName));

return Object.fromEntries(
// Paths come from require.resolve() and the dist folder listing, not from user input.
// eslint-disable-next-line security/detect-non-literal-fs-filename
readdirSync(distPath)
.filter(name => /^metafile-\w+\.json$/u.test(name))
.map(name => [
name,
// eslint-disable-next-line security/detect-non-literal-fs-filename
Object.entries(JSON.parse(readFileSync(join(distPath, name), 'utf8')).outputs)
.filter(([, { inputs }]) =>
Object.keys(inputs).some(input => input.includes('styles/dist/botframework-webchat-styles.react.'))
)
.map(([output]) => output)
])
);
}

test('botframework-webchat/internal.js should re-export the styles runtime of botframework-webchat-component', () => {
const bundleInternal = require('botframework-webchat/internal.js');
const componentInternal = require('botframework-webchat-component/internal.js');

expect(bundleInternal.CSSModuleThemeComposer).toBe(componentInternal.CSSModuleThemeComposer);
expect(bundleInternal.useStyles).toBe(componentInternal.useStyles);
});

test('botframework-webchat-component should bundle the styles runtime into one chunk per module format', () => {
const outputs = outputsWithStylesRuntime('botframework-webchat-component');

expect(Object.keys(outputs).sort()).toEqual(['metafile-cjs.json', 'metafile-esm.json']);
expect(outputs['metafile-cjs.json']).toHaveLength(1);
expect(outputs['metafile-esm.json']).toHaveLength(1);

// The marker used by the botframework-webchat-fluent-theme test below is in the styles runtime chunks.
[...outputs['metafile-cjs.json'], ...outputs['metafile-esm.json']].forEach(output =>
expect(
// Paths come from the esbuild metafile, not from user input.
// eslint-disable-next-line security/detect-non-literal-fs-filename
readFileSync(join(dirname(require.resolve('botframework-webchat-component')), '..', output), 'utf8')
).toContain(STYLES_RUNTIME_MARKER)
);
});

test('botframework-webchat should not bundle its own copy of the styles runtime', () =>
// IIFE builds (webchat.js) contain one copy, inside the botframework-webchat-component code they inline.
expect(outputsWithStylesRuntime('botframework-webchat')).toEqual({
'metafile-cjs.json': [],
'metafile-esm.json': [],
'metafile-iife.json': []
}));

test('botframework-webchat-fluent-theme should not bundle the styles runtime', () => {
// The package does not publish its esbuild metafiles ("files" in package.json), so check the published scripts.
const distPath = dirname(require.resolve('botframework-webchat-fluent-theme'));
const scripts = readdirSync(distPath).filter(name => /\.m?js$/u.test(name));

// CommonJS, ES modules, and the development and production IIFE builds; no chunks.
expect(scripts.sort()).toEqual([
'botframework-webchat-fluent-theme.development.js',
'botframework-webchat-fluent-theme.js',
'botframework-webchat-fluent-theme.mjs',
'botframework-webchat-fluent-theme.production.min.js'
]);
expect(
scripts.filter(name =>
// eslint-disable-next-line security/detect-non-literal-fs-filename
readFileSync(join(distPath, name), 'utf8').includes(STYLES_RUNTIME_MARKER)
)
).toEqual([]);
});
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { reactNode, validateProps } from '@msinternal/botframework-webchat-react-valibot';
import { useStyles } from '@msinternal/botframework-webchat-styles/react';
import cx from 'classnames';
import React, { Fragment, memo } from 'react';
import { boolean, object, optional, pipe, readonly, string, type InferInput } from 'valibot';

import { useVariantClassName } from '../../styles';
import { useStyles, useVariantClassName } from '../../styles';
import SlidingDots from '../assets/SlidingDots';
import styles from './ActivityLoader.module.css';

Expand Down
2 changes: 1 addition & 1 deletion packages/fluent-theme/src/components/icon/FluentIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { validateProps } from '@msinternal/botframework-webchat-react-valibot';
import { useStyles } from '@msinternal/botframework-webchat-styles/react';
import { createIconComponent } from 'botframework-webchat/internal.js';
import cx from 'classnames';
import React, { memo, useMemo, type CSSProperties } from 'react';
import { object, optional, pipe, readonly, string, union, type InferInput } from 'valibot';

import { useStyles } from '../../styles';
import styles from './FluentIcon.module.css';

const baseFluentIconPropsSchema = pipe(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useStyles } from '@msinternal/botframework-webchat-styles/react';
import { testIds } from 'botframework-webchat';
import cx from 'classnames';
import React, { memo } from 'react';

import { useStyles } from '../../styles';
import SlidingDots from '../assets/SlidingDots';
import styles from './SlidingDotsTypingIndicator.module.css';

Expand Down
4 changes: 2 additions & 2 deletions packages/fluent-theme/src/styles/useStyles.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
// TODO: refactor after initial approval gained
export { useStyles as default } from '@msinternal/botframework-webchat-styles/react';
// Use the copy from Web Chat, so Fluent elements read the same CSSModuleThemeContext as Web Chat elements.
export { useStyles as default } from 'botframework-webchat/internal.js';
8 changes: 8 additions & 0 deletions packages/styles/src/react/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
export { localClassName, webChatPrefixedClassName, type ClassNameTransform } from './private/classNameTransforms';
export { default as combineCSSModules } from './private/combineCSSModules';
export {
default as CSSModuleThemeComposer,
cssModuleThemeComposerPropsSchema,
type CSSModuleThemeComposerProps
} from './private/CSSModuleThemeComposer';
export { type CSSModuleTheme } from './private/CSSModuleThemeContext';
export { default as InjectStyleElements, injectStyleElementsPropsSchema } from './private/InjectStyleElements';
export { default as useStyles } from './private/useStyles';
Loading
Loading