diff --git a/CHANGELOG.md b/CHANGELOG.md index de8ad030b3..af40717856 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -90,6 +90,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Fixes [#4866](https://github.com/microsoft/BotFramework-WebChat/issues/4866). Citation modal show fill screen width on mobile device and various fit-and-finish, by [@compulim](https://github.com/compulim), in PR [#4867](https://github.com/microsoft/BotFramework-WebChat/pull/4867) - Fixes [#4878](https://github.com/microsoft/BotFramework-WebChat/issues/4878). `createStore` should return type of `Redux.Store`, by [@compulim](https://github.com/compulim), in PR [#4877](https://github.com/microsoft/BotFramework-WebChat/pull/4877) - Fixes [#4957](https://github.com/microsoft/BotFramework-WebChat/issues/4957). Native chevron of the accordion in citation should be hidden, by [@compulim](https://github.com/compulim), in PR [#4958](https://github.com/microsoft/BotFramework-WebChat/pull/4958) +- Fixes [#4870](https://github.com/microsoft/BotFramework-WebChat/issues/4870). Originator should use `claimInterpreter` instead of `ReplyAction/provider`, by [@compulim](https://github.com/compulim), in PR [#4910](https://github.com/microsoft/BotFramework-WebChat/pull/4910) ### Added diff --git a/__tests__/__image_snapshots__/html/badge-js-link-definition-should-display-text-ellipsis-1-snap.png b/__tests__/__image_snapshots__/html/badge-js-link-definition-should-display-text-ellipsis-1-snap.png new file mode 100644 index 0000000000..03942ac07b Binary files /dev/null and b/__tests__/__image_snapshots__/html/badge-js-link-definition-should-display-text-ellipsis-1-snap.png differ diff --git a/__tests__/__image_snapshots__/html/citation-accordion-js-citation-accordion-should-expand-and-collapse-on-click-1-snap.png b/__tests__/__image_snapshots__/html/citation-accordion-js-citation-accordion-should-expand-and-collapse-on-click-1-snap.png index 4c7c9181f8..fbe057cb8b 100644 Binary files a/__tests__/__image_snapshots__/html/citation-accordion-js-citation-accordion-should-expand-and-collapse-on-click-1-snap.png and b/__tests__/__image_snapshots__/html/citation-accordion-js-citation-accordion-should-expand-and-collapse-on-click-1-snap.png differ diff --git a/__tests__/__image_snapshots__/html/citation-accordion-js-citation-accordion-should-expand-and-collapse-on-click-3-snap.png b/__tests__/__image_snapshots__/html/citation-accordion-js-citation-accordion-should-expand-and-collapse-on-click-3-snap.png index 4c7c9181f8..fbe057cb8b 100644 Binary files a/__tests__/__image_snapshots__/html/citation-accordion-js-citation-accordion-should-expand-and-collapse-on-click-3-snap.png and b/__tests__/__image_snapshots__/html/citation-accordion-js-citation-accordion-should-expand-and-collapse-on-click-3-snap.png differ diff --git a/__tests__/__image_snapshots__/html/citation-basic-js-citation-should-display-1-snap.png b/__tests__/__image_snapshots__/html/citation-basic-js-citation-should-display-1-snap.png index 4c7c9181f8..fbe057cb8b 100644 Binary files a/__tests__/__image_snapshots__/html/citation-basic-js-citation-should-display-1-snap.png and b/__tests__/__image_snapshots__/html/citation-basic-js-citation-should-display-1-snap.png differ diff --git a/__tests__/__image_snapshots__/html/citation-show-modal-close-button-js-citation-modal-dialog-should-close-when-clicking-on-close-button-1-snap.png b/__tests__/__image_snapshots__/html/citation-show-modal-close-button-js-citation-modal-dialog-should-close-when-clicking-on-close-button-1-snap.png index 346730d066..f4a4d368ef 100644 Binary files a/__tests__/__image_snapshots__/html/citation-show-modal-close-button-js-citation-modal-dialog-should-close-when-clicking-on-close-button-1-snap.png and b/__tests__/__image_snapshots__/html/citation-show-modal-close-button-js-citation-modal-dialog-should-close-when-clicking-on-close-button-1-snap.png differ diff --git a/__tests__/__image_snapshots__/html/citation-show-modal-close-button-js-citation-modal-dialog-should-close-when-clicking-on-close-button-2-snap.png b/__tests__/__image_snapshots__/html/citation-show-modal-close-button-js-citation-modal-dialog-should-close-when-clicking-on-close-button-2-snap.png index d815b9a913..7216d2c56e 100644 Binary files a/__tests__/__image_snapshots__/html/citation-show-modal-close-button-js-citation-modal-dialog-should-close-when-clicking-on-close-button-2-snap.png and b/__tests__/__image_snapshots__/html/citation-show-modal-close-button-js-citation-modal-dialog-should-close-when-clicking-on-close-button-2-snap.png differ diff --git a/__tests__/__image_snapshots__/html/citation-show-modal-close-escape-js-citation-modal-dialog-should-close-when-escape-key-is-pressed-1-snap.png b/__tests__/__image_snapshots__/html/citation-show-modal-close-escape-js-citation-modal-dialog-should-close-when-escape-key-is-pressed-1-snap.png index 346730d066..f4a4d368ef 100644 Binary files a/__tests__/__image_snapshots__/html/citation-show-modal-close-escape-js-citation-modal-dialog-should-close-when-escape-key-is-pressed-1-snap.png and b/__tests__/__image_snapshots__/html/citation-show-modal-close-escape-js-citation-modal-dialog-should-close-when-escape-key-is-pressed-1-snap.png differ diff --git a/__tests__/__image_snapshots__/html/citation-show-modal-close-escape-js-citation-modal-dialog-should-close-when-escape-key-is-pressed-2-snap.png b/__tests__/__image_snapshots__/html/citation-show-modal-close-escape-js-citation-modal-dialog-should-close-when-escape-key-is-pressed-2-snap.png index d815b9a913..7216d2c56e 100644 Binary files a/__tests__/__image_snapshots__/html/citation-show-modal-close-escape-js-citation-modal-dialog-should-close-when-escape-key-is-pressed-2-snap.png and b/__tests__/__image_snapshots__/html/citation-show-modal-close-escape-js-citation-modal-dialog-should-close-when-escape-key-is-pressed-2-snap.png differ diff --git a/__tests__/__image_snapshots__/html/citation-show-modal-link-definitions-js-citation-modal-dialog-should-show-when-clicking-on-citation-in-link-definitions-1-snap.png b/__tests__/__image_snapshots__/html/citation-show-modal-link-definitions-js-citation-modal-dialog-should-show-when-clicking-on-citation-in-link-definitions-1-snap.png index 346730d066..f4a4d368ef 100644 Binary files a/__tests__/__image_snapshots__/html/citation-show-modal-link-definitions-js-citation-modal-dialog-should-show-when-clicking-on-citation-in-link-definitions-1-snap.png and b/__tests__/__image_snapshots__/html/citation-show-modal-link-definitions-js-citation-modal-dialog-should-show-when-clicking-on-citation-in-link-definitions-1-snap.png differ diff --git a/__tests__/__image_snapshots__/html/citation-show-modal-link-definitions-js-citation-modal-dialog-should-show-when-clicking-on-citation-in-link-definitions-2-snap.png b/__tests__/__image_snapshots__/html/citation-show-modal-link-definitions-js-citation-modal-dialog-should-show-when-clicking-on-citation-in-link-definitions-2-snap.png index 9e0380631a..09d7171f43 100644 Binary files a/__tests__/__image_snapshots__/html/citation-show-modal-link-definitions-js-citation-modal-dialog-should-show-when-clicking-on-citation-in-link-definitions-2-snap.png and b/__tests__/__image_snapshots__/html/citation-show-modal-link-definitions-js-citation-modal-dialog-should-show-when-clicking-on-citation-in-link-definitions-2-snap.png differ diff --git a/__tests__/__image_snapshots__/html/citation-show-modal-markdown-js-citation-modal-dialog-should-show-when-clicking-on-citation-in-markdown-1-snap.png b/__tests__/__image_snapshots__/html/citation-show-modal-markdown-js-citation-modal-dialog-should-show-when-clicking-on-citation-in-markdown-1-snap.png index 346730d066..f4a4d368ef 100644 Binary files a/__tests__/__image_snapshots__/html/citation-show-modal-markdown-js-citation-modal-dialog-should-show-when-clicking-on-citation-in-markdown-1-snap.png and b/__tests__/__image_snapshots__/html/citation-show-modal-markdown-js-citation-modal-dialog-should-show-when-clicking-on-citation-in-markdown-1-snap.png differ diff --git a/__tests__/__image_snapshots__/html/citation-show-modal-markdown-js-citation-modal-dialog-should-show-when-clicking-on-citation-in-markdown-2-snap.png b/__tests__/__image_snapshots__/html/citation-show-modal-markdown-js-citation-modal-dialog-should-show-when-clicking-on-citation-in-markdown-2-snap.png index 9e0380631a..09d7171f43 100644 Binary files a/__tests__/__image_snapshots__/html/citation-show-modal-markdown-js-citation-modal-dialog-should-show-when-clicking-on-citation-in-markdown-2-snap.png and b/__tests__/__image_snapshots__/html/citation-show-modal-markdown-js-citation-modal-dialog-should-show-when-clicking-on-citation-in-markdown-2-snap.png differ diff --git a/__tests__/__image_snapshots__/html/citation-show-modal-width-desktop-js-citation-modal-dialog-should-show-60-on-desktop-1-snap.png b/__tests__/__image_snapshots__/html/citation-show-modal-width-desktop-js-citation-modal-dialog-should-show-60-on-desktop-1-snap.png index 75e8aa7530..93c24c8b91 100644 Binary files a/__tests__/__image_snapshots__/html/citation-show-modal-width-desktop-js-citation-modal-dialog-should-show-60-on-desktop-1-snap.png and b/__tests__/__image_snapshots__/html/citation-show-modal-width-desktop-js-citation-modal-dialog-should-show-60-on-desktop-1-snap.png differ diff --git a/__tests__/__image_snapshots__/html/citation-show-modal-width-desktop-js-citation-modal-dialog-should-show-60-on-desktop-2-snap.png b/__tests__/__image_snapshots__/html/citation-show-modal-width-desktop-js-citation-modal-dialog-should-show-60-on-desktop-2-snap.png index 5aa7747814..472efe68c5 100644 Binary files a/__tests__/__image_snapshots__/html/citation-show-modal-width-desktop-js-citation-modal-dialog-should-show-60-on-desktop-2-snap.png and b/__tests__/__image_snapshots__/html/citation-show-modal-width-desktop-js-citation-modal-dialog-should-show-60-on-desktop-2-snap.png differ diff --git a/__tests__/__image_snapshots__/html/citation-show-modal-width-mobile-js-citation-modal-dialog-should-show-full-width-on-mobile-device-1-snap.png b/__tests__/__image_snapshots__/html/citation-show-modal-width-mobile-js-citation-modal-dialog-should-show-full-width-on-mobile-device-1-snap.png index 4c7c9181f8..fbe057cb8b 100644 Binary files a/__tests__/__image_snapshots__/html/citation-show-modal-width-mobile-js-citation-modal-dialog-should-show-full-width-on-mobile-device-1-snap.png and b/__tests__/__image_snapshots__/html/citation-show-modal-width-mobile-js-citation-modal-dialog-should-show-full-width-on-mobile-device-1-snap.png differ diff --git a/__tests__/__image_snapshots__/html/citation-show-modal-width-mobile-js-citation-modal-dialog-should-show-full-width-on-mobile-device-2-snap.png b/__tests__/__image_snapshots__/html/citation-show-modal-width-mobile-js-citation-modal-dialog-should-show-full-width-on-mobile-device-2-snap.png index 346730d066..f4a4d368ef 100644 Binary files a/__tests__/__image_snapshots__/html/citation-show-modal-width-mobile-js-citation-modal-dialog-should-show-full-width-on-mobile-device-2-snap.png and b/__tests__/__image_snapshots__/html/citation-show-modal-width-mobile-js-citation-modal-dialog-should-show-full-width-on-mobile-device-2-snap.png differ diff --git a/__tests__/__image_snapshots__/html/provenance-basic-js-provenance-should-display-1-snap.png b/__tests__/__image_snapshots__/html/claim-interpreter-js-originator-using-claim-interpreter-should-display-1-snap.png similarity index 100% rename from __tests__/__image_snapshots__/html/provenance-basic-js-provenance-should-display-1-snap.png rename to __tests__/__image_snapshots__/html/claim-interpreter-js-originator-using-claim-interpreter-should-display-1-snap.png diff --git a/__tests__/__image_snapshots__/html/identifier-as-string-js-link-definition-should-display-identifier-of-type-string-1-snap.png b/__tests__/__image_snapshots__/html/identifier-as-string-js-link-definition-should-display-identifier-of-type-string-1-snap.png new file mode 100644 index 0000000000..fe21654123 Binary files /dev/null and b/__tests__/__image_snapshots__/html/identifier-as-string-js-link-definition-should-display-identifier-of-type-string-1-snap.png differ diff --git a/__tests__/__image_snapshots__/html/reference-js-link-definition-should-reference-sample-1-snap.png b/__tests__/__image_snapshots__/html/reference-js-link-definition-should-reference-sample-1-snap.png new file mode 100644 index 0000000000..cbed31026e Binary files /dev/null and b/__tests__/__image_snapshots__/html/reference-js-link-definition-should-reference-sample-1-snap.png differ diff --git a/__tests__/__image_snapshots__/html/reply-action-js-originator-using-reply-action-should-display-1-snap.png b/__tests__/__image_snapshots__/html/reply-action-js-originator-using-reply-action-should-display-1-snap.png new file mode 100644 index 0000000000..b2f5044c72 Binary files /dev/null and b/__tests__/__image_snapshots__/html/reply-action-js-originator-using-reply-action-should-display-1-snap.png differ diff --git a/__tests__/__image_snapshots__/html/wrap-zero-width-space-js-link-definition-should-word-wrap-pure-identifier-to-next-line-but-not-text-content-1-snap.png b/__tests__/__image_snapshots__/html/wrap-zero-width-space-js-link-definition-should-word-wrap-pure-identifier-to-next-line-but-not-text-content-1-snap.png new file mode 100644 index 0000000000..c4bf3863dc Binary files /dev/null and b/__tests__/__image_snapshots__/html/wrap-zero-width-space-js-link-definition-should-word-wrap-pure-identifier-to-next-line-but-not-text-content-1-snap.png differ diff --git a/__tests__/hooks/useRenderMarkdownAsHTML.js b/__tests__/hooks/useRenderMarkdownAsHTML.js index b20b06f984..ef1a95af4f 100644 --- a/__tests__/hooks/useRenderMarkdownAsHTML.js +++ b/__tests__/hooks/useRenderMarkdownAsHTML.js @@ -41,7 +41,7 @@ test('renderMarkdown should add accessibility text for external links', async () await expect( pageObjects.runHook('useRenderMarkdownAsHTML', [], fn => fn('Click [here](https://aka.ms/) to find out more.')) ).resolves.toMatchInlineSnapshot(` - "
Click here to find out more.
+ "Click \u200Bhere\u200B to find out more.
" `); }); @@ -52,7 +52,7 @@ test('renderMarkdown should add accessibility text for external links with yue', await expect( pageObjects.runHook('useRenderMarkdownAsHTML', [], fn => fn('Click [here](https://aka.ms/) to find out more.')) ).resolves.toMatchInlineSnapshot(` - "Click here to find out more.
+ "Click \u200Bhere\u200B to find out more.
" `); }); diff --git a/__tests__/html/accessibility.heroCard.heading.html b/__tests__/html/accessibility.heroCard.heading.html index de025141c2..041af67cf2 100644 --- a/__tests__/html/accessibility.heroCard.heading.html +++ b/__tests__/html/accessibility.heroCard.heading.html @@ -25,7 +25,7 @@ expect(document.querySelector('.ac-textBlock[role="heading"]')).toHaveProperty( 'innerText', - 'Details about image 1' + '\u200BDetails about image 1\u200B' ); }); diff --git a/__tests__/html/linkDefinition/badge.html b/__tests__/html/linkDefinition/badge.html new file mode 100644 index 0000000000..0882140703 --- /dev/null +++ b/__tests__/html/linkDefinition/badge.html @@ -0,0 +1,148 @@ + + + + + + + + + + + + + diff --git a/__tests__/html/linkDefinition/badge.js b/__tests__/html/linkDefinition/badge.js new file mode 100644 index 0000000000..a0b319ac03 --- /dev/null +++ b/__tests__/html/linkDefinition/badge.js @@ -0,0 +1,5 @@ +/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */ + +describe('link definition', () => { + test('should display text ellipsis', () => runHTML('linkDefinition/badge.html')); +}); diff --git a/__tests__/html/linkDefinition/identifierAsString.html b/__tests__/html/linkDefinition/identifierAsString.html new file mode 100644 index 0000000000..181aba3a76 --- /dev/null +++ b/__tests__/html/linkDefinition/identifierAsString.html @@ -0,0 +1,63 @@ + + + + + + + + + + + + + diff --git a/__tests__/html/linkDefinition/identifierAsString.js b/__tests__/html/linkDefinition/identifierAsString.js new file mode 100644 index 0000000000..021ad74a0b --- /dev/null +++ b/__tests__/html/linkDefinition/identifierAsString.js @@ -0,0 +1,5 @@ +/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */ + +describe('link definition', () => { + test('should display identifier of type string', () => runHTML('linkDefinition/identifierAsString.html')); +}); diff --git a/__tests__/html/linkDefinition/reference.html b/__tests__/html/linkDefinition/reference.html new file mode 100644 index 0000000000..05001f60c9 --- /dev/null +++ b/__tests__/html/linkDefinition/reference.html @@ -0,0 +1,168 @@ + + + + + + + + + + + + + diff --git a/__tests__/html/linkDefinition/reference.js b/__tests__/html/linkDefinition/reference.js new file mode 100644 index 0000000000..9f9f92b6d4 --- /dev/null +++ b/__tests__/html/linkDefinition/reference.js @@ -0,0 +1,5 @@ +/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */ + +describe('link definition', () => { + test('should reference sample', () => runHTML('linkDefinition/reference.html')); +}); diff --git a/__tests__/html/linkDefinition/wrapZeroWidthSpace.html b/__tests__/html/linkDefinition/wrapZeroWidthSpace.html new file mode 100644 index 0000000000..a8ae5f5c0a --- /dev/null +++ b/__tests__/html/linkDefinition/wrapZeroWidthSpace.html @@ -0,0 +1,34 @@ + + + + + + + + + + + + + diff --git a/__tests__/html/linkDefinition/wrapZeroWidthSpace.js b/__tests__/html/linkDefinition/wrapZeroWidthSpace.js new file mode 100644 index 0000000000..b76d010668 --- /dev/null +++ b/__tests__/html/linkDefinition/wrapZeroWidthSpace.js @@ -0,0 +1,5 @@ +/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */ + +describe('link definition', () => { + test('should word-wrap pure identifier to next line but not text content', () => runHTML('linkDefinition/wrapZeroWidthSpace.html')); +}); diff --git a/__tests__/html/markdown.attributes.curlyBrackets.html b/__tests__/html/markdown.attributes.curlyBrackets.html index 7afe8d5642..c752f6217c 100644 --- a/__tests__/html/markdown.attributes.curlyBrackets.html +++ b/__tests__/html/markdown.attributes.curlyBrackets.html @@ -1,4 +1,4 @@ - + @@ -85,9 +85,10 @@ notRecognizedItems, otherCasesNotProcessedItems, otherCasesProcessedItems - ] = [].map.call(document.querySelectorAll('.webchat__bubble__content .webchat__text-content__markdown ul'), list => [ - ...list.querySelectorAll('li') - ]); + ] = [].map.call( + document.querySelectorAll('.webchat__bubble__content .webchat__text-content__markdown ul'), + list => [...list.querySelectorAll('li')] + ); // THEN: Hello {World} and Hello {1} should be kept as-is. expect(regressionItems.shift()).toHaveProperty('innerText', 'Hello {World}'); @@ -100,33 +101,33 @@ // [Link](https://bing.com/){aria-label="This is a label"} should return ` expect(supportedItems.shift()).toHaveProperty( 'innerText', - 'Link should return ' + '\u200BLink\u200B should return ' ); expect(anchors.shift()).toHaveAttributes({ 'aria-label': 'This is a label' }); // [Link](https://bing.com/){aria-label=Hello} should return `` - expect(supportedItems.shift()).toHaveProperty('innerText', 'Link should return '); + expect(supportedItems.shift()).toHaveProperty('innerText', '\u200BLink\u200B should return '); expect(anchors.shift()).toHaveAttributes({ 'aria-label': 'Hello' }); // [Link](https://bing.com/){aria-label=} should return `` - expect(supportedItems.shift()).toHaveProperty('innerText', 'Link should return '); + expect(supportedItems.shift()).toHaveProperty('innerText', '\u200BLink\u200B should return '); expect(anchors.shift()).toHaveAttributes({ 'aria-label': 0 }); // [Link](https://bing.com/){aria-label} should return `` - expect(supportedItems.shift()).toHaveProperty('innerText', 'Link should return '); + expect(supportedItems.shift()).toHaveProperty('innerText', '\u200BLink\u200B should return '); expect(anchors.shift()).toHaveAttributes({ 'aria-label': 0 }); // [Link](https://bing.com/){ aria-label=" This is a label with many whitespaces " } should return `` // Spaces before `aria-label` or after the last quote, is supported by `markdown-it-attrs`. expect(supportedItems.shift()).toHaveProperty( 'innerText', - 'Link should return ' + '\u200BLink\u200B should return ' ); expect(anchors.shift()).toHaveAttributes({ 'aria-label': ' This is a label with many whitespaces ' }); // [Link](https://bing.com/){aria-label=a"b"c} should return `` // `aria-label=a"b"c` is supported by `markdown-it-attrs`, will turn into `aria-label="a"b"c"`. - expect(supportedItems.shift()).toHaveProperty('innerText', 'Link should return '); + expect(supportedItems.shift()).toHaveProperty('innerText', '\u200BLink\u200B should return '); expect(anchors.shift()).toHaveAttributes({ 'aria-label': 'a"b"c' }); // THEN: Invalid or unrecognized curly brackets should left untouched. @@ -137,16 +138,16 @@ // Although "aria-label=This" is recognized, "is ignored" is not valid. The whole bracelet is ignored. expect(notRecognizedItems.shift()).toHaveProperty( 'innerText', - 'Link{aria-label=This is ignored} should left untouched' + '\u200BLink\u200B{aria-label=This is ignored} should left untouched' ); // [Link](https://bing.com/){aria-label ="This is a label with whitespace before equal sign"} should left untouched // A space between or after the equal sign (=) is not valid in `markdown-it-attrs`. expect(notRecognizedItems.shift()).toHaveProperty( 'innerText', - 'Link{aria-label =“This is a label with whitespace before equal sign”} should left untouched' + '\u200BLink\u200B{aria-label =“This is a label with whitespace before equal sign”} should left untouched' ); - expect(notRecognizedItems.shift()).toHaveProperty('innerText', 'Link{.ignored} should left untouched'); + expect(notRecognizedItems.shift()).toHaveProperty('innerText', '\u200BLink\u200B{.ignored} should left untouched'); // [Link](https://bing.com/){.ignored} should left untouched // Class is not supported in Web Chat. @@ -156,7 +157,7 @@ // "onload" is not a recognized attribute. expect(notRecognizedItems.shift()).toHaveProperty( 'innerText', - 'Link{onload=“javascript:void()”} should left untouched' + '\u200BLink\u200B{onload=“javascript:void()”} should left untouched' ); // THEN: Curly brackets not processed by `markdown-it-attrs` should left untouched. diff --git a/__tests__/html/provenance.basic.html b/__tests__/html/originator/claimInterpreter.html similarity index 71% rename from __tests__/html/provenance.basic.html rename to __tests__/html/originator/claimInterpreter.html index 33166e7c7a..6443341bde 100644 --- a/__tests__/html/provenance.basic.html +++ b/__tests__/html/originator/claimInterpreter.html @@ -26,12 +26,11 @@ entities: [ { '@context': 'https://schema.org', - '@type': 'ReplyAction', - type: 'https://schema.org/ReplyAction', - provider: { - '@context': 'https://schema.org', + '@type': 'Claim', + type: 'https://schema.org/Claim', + claimInterpreter: { '@type': 'Project', - name: 'Surfaced by Azure OpenAI', + slogan: 'Surfaced by Azure OpenAI', url: 'https://www.microsoft.com/en-us/ai/responsible-ai' } } @@ -43,12 +42,12 @@ await host.snapshot(); const [activityStatus] = pageElements.activityStatuses(); - const provenanceLink = activityStatus.querySelector('a'); + const originatorLink = activityStatus.querySelector('a'); - expect(provenanceLink).toBeTruthy(); - expect(provenanceLink.getAttribute('href')).toBe('https://www.microsoft.com/en-us/ai/responsible-ai'); - expect(provenanceLink.getAttribute('rel')).toBe('noopener noreferrer'); - expect(provenanceLink.getAttribute('target')).toBe('_blank'); + expect(originatorLink).toBeTruthy(); + expect(originatorLink.getAttribute('href')).toBe('https://www.microsoft.com/en-us/ai/responsible-ai'); + expect(originatorLink.getAttribute('rel')).toBe('noopener noreferrer'); + expect(originatorLink.getAttribute('target')).toBe('_blank'); }); diff --git a/__tests__/html/originator/claimInterpreter.js b/__tests__/html/originator/claimInterpreter.js new file mode 100644 index 0000000000..429b048258 --- /dev/null +++ b/__tests__/html/originator/claimInterpreter.js @@ -0,0 +1,5 @@ +/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */ + +describe('originator using claimInterpreter', () => { + test('should display', () => runHTML('originator/claimInterpreter.html')); +}); diff --git a/__tests__/html/originator/replyAction.html b/__tests__/html/originator/replyAction.html new file mode 100644 index 0000000000..4ddf98d2d0 --- /dev/null +++ b/__tests__/html/originator/replyAction.html @@ -0,0 +1,66 @@ + + + + + + + + + + + + + diff --git a/__tests__/html/originator/replyAction.js b/__tests__/html/originator/replyAction.js new file mode 100644 index 0000000000..eff0d96f1d --- /dev/null +++ b/__tests__/html/originator/replyAction.js @@ -0,0 +1,5 @@ +/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */ + +describe('originator using ReplyAction', () => { + test('should display', () => runHTML('originator/replyAction.html')); +}); diff --git a/__tests__/html/provenance.basic.js b/__tests__/html/provenance.basic.js deleted file mode 100644 index 8324e08f2f..0000000000 --- a/__tests__/html/provenance.basic.js +++ /dev/null @@ -1,5 +0,0 @@ -/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */ - -describe('provenance', () => { - test('should display', () => runHTML('provenance.basic.html')); -}); diff --git a/packages/bundle/src/__tests__/renderMarkdown.spec.js b/packages/bundle/src/__tests__/renderMarkdown.spec.js index 2ddde87410..927e51785d 100644 --- a/packages/bundle/src/__tests__/renderMarkdown.spec.js +++ b/packages/bundle/src/__tests__/renderMarkdown.spec.js @@ -54,7 +54,7 @@ describe('renderMarkdown', () => { expect(renderMarkdown('[example](https://sample.com){aria-label="Sample label"}', styleOptions)) .toMatchInlineSnapshot(` - " + "\u200Bexample\u200B
" `); }); @@ -65,7 +65,7 @@ describe('renderMarkdown', () => { expect(renderMarkdown('[example](https://sample.com){aria-label="Sample label"}', styleOptions, options)) .toMatchInlineSnapshot(` - " + "\u200Bexample\u200B
" `); }); @@ -74,7 +74,7 @@ describe('renderMarkdown', () => { const styleOptions = { markdownRespectCRLF: true }; expect(renderMarkdown(`[example@test.com](sip:example@test.com)`, styleOptions)).toBe( - '\n' + '\u200Bexample@test.com\u200B
\n' ); }); @@ -82,7 +82,7 @@ describe('renderMarkdown', () => { const styleOptions = { markdownRespectCRLF: true }; expect(renderMarkdown(`[(505)503-4455](tel:505-503-4455)`, styleOptions)).toBe( - '\n' + '\u200B(505)503-4455\u200B
\n' ); }); diff --git a/packages/bundle/src/markdown/markdownItPlugins/betterLink.ts b/packages/bundle/src/markdown/markdownItPlugins/betterLink.ts index e841bef644..5ff035d1fb 100644 --- a/packages/bundle/src/markdown/markdownItPlugins/betterLink.ts +++ b/packages/bundle/src/markdown/markdownItPlugins/betterLink.ts @@ -1,5 +1,5 @@ -import iterator from 'markdown-it-for-inline'; import MarkdownIt from 'markdown-it'; +import iterator from 'markdown-it-for-inline'; // Put a transparent pixel instead of the "open in new window" icon, so developers can easily modify the icon in CSS. const TRANSPARENT_GIF = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; @@ -30,11 +30,19 @@ type Decoration = { /** Value of "title" attribute of the link. If set to `false`, remove existing attribute. */ title?: AttributeSetter; + + /** Wraps the link with zero-width space. */ + wrapZeroWidthSpace?: boolean; }; // This is used for parsing Markdown for external links. const internalMarkdownIt = new MarkdownIt(); +const ZERO_WIDTH_SPACE_TOKEN = { + content: '\u200b', + type: 'text' +}; + function setTokenAttribute(attrs: Array<[string, string]>, name: string, value?: AttributeSetter) { const index = attrs.findIndex(entry => entry[0] === name); @@ -61,52 +69,65 @@ const betterLink = ( ): typeof MarkdownIt => markdown.use(iterator, 'url_new_win', 'link_open', (tokens, index) => { const indexOfLinkCloseToken = tokens.indexOf(tokens.slice(index + 1).find(({ type }) => type === 'link_close')); - const token = tokens[+index]; + // eslint-disable-next-line no-magic-numbers + const updatedTokens = tokens.splice(index, ~indexOfLinkCloseToken ? indexOfLinkCloseToken - index + 1 : 2); - const [, href] = token.attrs.find(([name]) => name === 'href'); - const nodesInLink = tokens.slice(index + 1, indexOfLinkCloseToken); + try { + const [linkOpenToken] = updatedTokens; + const linkCloseToken = updatedTokens[updatedTokens.length - 1]; - const textContent = nodesInLink - .filter(({ type }) => type === 'text') - .map(({ content }) => content) - .join(' '); + const [, href] = linkOpenToken.attrs.find(([name]) => name === 'href'); + const nodesInLink = updatedTokens.slice(1, updatedTokens.length - 1); - const decoration = decorate(href, textContent); + const textContent = nodesInLink + .filter(({ type }) => type === 'text') + .map(({ content }) => content) + .join(' '); - if (!decoration) { - return; - } + const decoration = decorate(href, textContent); - const { ariaLabel, asButton, className, iconAlt, iconClassName, rel, target, title } = decoration; + if (!decoration) { + return; + } - setTokenAttribute(token.attrs, 'aria-label', ariaLabel); - setTokenAttribute(token.attrs, 'class', className); - setTokenAttribute(token.attrs, 'title', title); + const { ariaLabel, asButton, className, iconAlt, iconClassName, rel, target, title, wrapZeroWidthSpace } = + decoration; - if (iconClassName) { - const iconTokens = internalMarkdownIt.parseInline(`![](${TRANSPARENT_GIF})`)[0].children; + setTokenAttribute(linkOpenToken.attrs, 'aria-label', ariaLabel); + setTokenAttribute(linkOpenToken.attrs, 'class', className); + setTokenAttribute(linkOpenToken.attrs, 'title', title); - setTokenAttribute(iconTokens[0].attrs, 'class', iconClassName); - setTokenAttribute(iconTokens[0].attrs, 'title', iconAlt); + if (iconClassName) { + const iconTokens = internalMarkdownIt.parseInline(`![](${TRANSPARENT_GIF})`)[0].children; - // Add an icon before . - ~indexOfLinkCloseToken && tokens.splice(indexOfLinkCloseToken, 0, ...iconTokens); - } + setTokenAttribute(iconTokens[0].attrs, 'class', iconClassName); + setTokenAttribute(iconTokens[0].attrs, 'title', iconAlt); + + // Add an icon before . + // eslint-disable-next-line no-magic-numbers + updatedTokens.splice(-1, 0, ...iconTokens); + } - if (asButton) { - setTokenAttribute(token.attrs, 'href', false); + if (asButton) { + setTokenAttribute(linkOpenToken.attrs, 'href', false); - token.tag = 'button'; + linkOpenToken.tag = 'button'; - setTokenAttribute(token.attrs, 'type', 'button'); - setTokenAttribute(token.attrs, 'value', href); + setTokenAttribute(linkOpenToken.attrs, 'type', 'button'); + setTokenAttribute(linkOpenToken.attrs, 'value', href); - if (~indexOfLinkCloseToken) { - tokens[+indexOfLinkCloseToken].tag = 'button'; + linkCloseToken.tag = 'button'; + } else { + setTokenAttribute(linkOpenToken.attrs, 'rel', rel); + setTokenAttribute(linkOpenToken.attrs, 'target', target); } - } else { - setTokenAttribute(token.attrs, 'rel', rel); - setTokenAttribute(token.attrs, 'target', target); + + if (wrapZeroWidthSpace) { + updatedTokens.splice(0, 0, ZERO_WIDTH_SPACE_TOKEN); + updatedTokens.splice(Infinity, 0, ZERO_WIDTH_SPACE_TOKEN); + } + } finally { + tokens.splice(index, 0, ...updatedTokens); } }); diff --git a/packages/bundle/src/markdown/renderMarkdown.ts b/packages/bundle/src/markdown/renderMarkdown.ts index 846fa0f848..d3a63e3494 100644 --- a/packages/bundle/src/markdown/renderMarkdown.ts +++ b/packages/bundle/src/markdown/renderMarkdown.ts @@ -2,9 +2,9 @@ import { onErrorResumeNext } from 'botframework-webchat-core'; import MarkdownIt from 'markdown-it'; import sanitizeHTML from 'sanitize-html'; -import { pre as respectCRLFPre } from './markdownItPlugins/respectCRLF'; import ariaLabel, { post as ariaLabelPost, pre as ariaLabelPre } from './markdownItPlugins/ariaLabel'; import betterLink from './markdownItPlugins/betterLink'; +import { pre as respectCRLFPre } from './markdownItPlugins/respectCRLF'; import iterateLinkDefinitions from './private/iterateLinkDefinitions'; const SANITIZE_HTML_OPTIONS = Object.freeze({ @@ -88,7 +88,8 @@ export default function render( .use(betterLink, (href: string, textContent: string): BetterLinkDecoration | undefined => { const decoration: BetterLinkDecoration = { rel: 'noopener noreferrer', - target: '_blank' + target: '_blank', + wrapZeroWidthSpace: true }; const ariaLabelSegments: string[] = [textContent]; @@ -101,10 +102,12 @@ export default function render( linkDefinition.title || onErrorResumeNext(() => new URL(linkDefinition.url).host) || linkDefinition.url ); - linkDefinition.identifier === textContent && classes.add('webchat__render-markdown__pure-identifier'); + // linkDefinition.identifier is uppercase, while linkDefinition.label is as-is. + linkDefinition.label === textContent && classes.add('webchat__render-markdown__pure-identifier'); } - if (protocol === 'cite:') { + // For links that would be sanitized out, let's turn them into a button so we could handle them later. + if (!SANITIZE_HTML_OPTIONS.allowedSchemes.map(scheme => `${scheme}:`).includes(protocol)) { decoration.asButton = true; classes.add('webchat__render-markdown__citation'); diff --git a/packages/component/package-lock.json b/packages/component/package-lock.json index a21ebbfb44..4e77c87c41 100644 --- a/packages/component/package-lock.json +++ b/packages/component/package-lock.json @@ -13,6 +13,7 @@ "base64-js": "1.5.1", "classnames": "2.3.2", "compute-scroll-into-view": "1.0.20", + "deep-freeze-strict": "^1.1.1", "event-target-shim": "6.0.2", "markdown-it": "13.0.2", "math-random": "2.0.1", @@ -2771,6 +2772,11 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/deep-freeze-strict": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-freeze-strict/-/deep-freeze-strict-1.1.1.tgz", + "integrity": "sha512-QemROZMM2IvhAcCFvahdX2Vbm4S/txeq5rFYU9fh4mQP79WTMW5c/HkQ2ICl1zuzcDZdPZ6zarDxQeQMsVYoNA==" + }, "node_modules/define-properties": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", @@ -6892,6 +6898,11 @@ "character-entities": "^2.0.0" } }, + "deep-freeze-strict": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-freeze-strict/-/deep-freeze-strict-1.1.1.tgz", + "integrity": "sha512-QemROZMM2IvhAcCFvahdX2Vbm4S/txeq5rFYU9fh4mQP79WTMW5c/HkQ2ICl1zuzcDZdPZ6zarDxQeQMsVYoNA==" + }, "define-properties": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", diff --git a/packages/component/package.json b/packages/component/package.json index 3db8cb0958..7cbeeb3b8f 100644 --- a/packages/component/package.json +++ b/packages/component/package.json @@ -100,6 +100,7 @@ "botframework-webchat-core": "0.0.0-0", "classnames": "2.3.2", "compute-scroll-into-view": "1.0.20", + "deep-freeze-strict": "^1.1.1", "event-target-shim": "6.0.2", "markdown-it": "13.0.2", "math-random": "2.0.1", diff --git a/packages/component/src/ActivityStatus/OthersActivityStatus.tsx b/packages/component/src/ActivityStatus/OthersActivityStatus.tsx index ac76fb25ff..6ab15ed222 100644 --- a/packages/component/src/ActivityStatus/OthersActivityStatus.tsx +++ b/packages/component/src/ActivityStatus/OthersActivityStatus.tsx @@ -1,54 +1,80 @@ -import { type WebChatActivity } from 'botframework-webchat-core'; +import { + getOrgSchemaMessage, + OrgSchemaAction, + OrgSchemaProject, + parseAction, + parseClaim, + warnOnce, + type WebChatActivity +} from 'botframework-webchat-core'; import classNames from 'classnames'; -import React, { memo, type ReactNode, useMemo } from 'react'; +import React, { memo, useMemo, type ReactNode } from 'react'; -import { isReplyAction, type ReplyAction } from '../types/external/OrgSchema/ReplyAction'; -import { isThing, type Thing } from '../types/external/OrgSchema/Thing'; -import { isVoteAction, type VoteAction } from '../types/external/OrgSchema/VoteAction'; -import { type TypeOfArray } from '../types/internal/TypeOfArray'; +import useStyleSet from '../hooks/useStyleSet'; +import dereferenceBlankNodes from '../Utils/JSONLinkedData/dereferenceBlankNodes'; import Feedback from './private/Feedback/Feedback'; import Originator from './private/Originator'; import Slotted from './Slotted'; import Timestamp from './Timestamp'; -import useStyleSet from '../hooks/useStyleSet'; -type WebChatEntity = TypeOfArray