Skip to content
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

Convert typed emoticons into Emoji in the TextBox #3303

Closed
wants to merge 17 commits into from
Closed
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

- Resolves [#3182](https://github.com/microsoft/BotFramework-WebChat/issues/3182). Added stacked suggested actions height properties, by [@corinagum](https://github.com/corinagum), in PR [#3235](https://github.com/microsoft/BotFramework-WebChat/pull/3235)
- Localized strings in Cantonese (`yue`), by [@compulim](https://github.com/compulim), in PR [#3289](https://github.com/microsoft/BotFramework-WebChat/pull/3289)
- Convert typed emoticons into Emoji in the Textbox: [#3249](https://github.com/microsoft/BotFramework-WebChat/issues/3249), by [@corinagum](https://github.com/corinagum), in PR [#3303](https://github.com/microsoft/BotFramework-WebChat/pull/3303)

### Fixes

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.
68 changes: 68 additions & 0 deletions __tests__/emoji.copypaste.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { Key } from 'selenium-webdriver';

import { imageSnapshotOptions, timeouts } from './constants.json';
import getTranscript from './setup/elements/getTranscript.js';
import uiConnected from './setup/conditions/uiConnected';

// selenium-webdriver API doc:
// https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html

jest.setTimeout(timeouts.test);

test('Correct emoticon should be replaced as emoji when copy/pasted from clipboard', async () => {
const { driver, pageObjects } = await setupWebDriver({
props: {
styleOptions: { emojiSet: true }
}
});

// create input text and copy to clipboard
await pageObjects.sendTextToClipboard(':) <3');

await driver.wait(uiConnected(), timeouts.directLine);

const transcript = await getTranscript(driver);

await transcript.click();

await driver
.actions()
.keyDown(Key.CONTROL)
.sendKeys('v')
.keyUp(Key.CONTROL)
.perform();

const base64PNG = await driver.takeScreenshot();

expect(base64PNG).toMatchImageSnapshot(imageSnapshotOptions);
});

test('Emoticons appended after copy/pasted will still be replaced as emoji', async () => {
const { driver, pageObjects } = await setupWebDriver({
props: {
styleOptions: { emojiSet: true }
}
});

// create input text and copy to clipboard
await pageObjects.sendTextToClipboard(':) <3');

await driver.wait(uiConnected(), timeouts.directLine);

const transcript = await getTranscript(driver);

await transcript.click();

await driver
.actions()
.keyDown(Key.CONTROL)
.sendKeys('v')
.keyUp(Key.CONTROL)
.perform();

await pageObjects.typeInSendBox(':o');

const base64PNG = await driver.takeScreenshot();

expect(base64PNG).toMatchImageSnapshot(imageSnapshotOptions);
});
82 changes: 82 additions & 0 deletions __tests__/emoji.escapeChar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { Key } from 'selenium-webdriver';

import { imageSnapshotOptions, timeouts } from './constants.json';
import getTranscript from './setup/elements/getTranscript.js';
import uiConnected from './setup/conditions/uiConnected';

// selenium-webdriver API doc:
// https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html

jest.setTimeout(timeouts.test);

test('Copy/pasted text with incidental emoticons will (undesirably) replace emoticons with emoji', async () => {
const { driver, pageObjects } = await setupWebDriver({
props: {
styleOptions: { emojiSet: { '):': '☹️' } }
}
});

// create input text and copy to clipboard
await pageObjects.sendTextToClipboard('function enabled(): boolean { return true; }');

await driver.wait(uiConnected(), timeouts.directLine);

const transcript = await getTranscript(driver);

await transcript.click();

await driver
.actions()
.keyDown(Key.CONTROL)
.sendKeys('v')
.keyUp(Key.CONTROL)
.perform();

const base64PNG = await driver.takeScreenshot();

expect(base64PNG).toMatchImageSnapshot(imageSnapshotOptions);
});

test('Copy/pasted text with escaped emoticons will replace to fully escaped emoticon', async () => {
const { driver, pageObjects } = await setupWebDriver({
props: {
styleOptions: { emojiSet: { '):': '☹️' } }
}
});

// create input text and copy to clipboard
await pageObjects.sendTextToClipboard('function enabled(\\): boolean { return true; }');

await driver.wait(uiConnected(), timeouts.directLine);

const transcript = await getTranscript(driver);

await transcript.click();

await driver
.actions()
.keyDown(Key.CONTROL)
.sendKeys('v')
.keyUp(Key.CONTROL)
.perform();

const base64PNG = await driver.takeScreenshot();

expect(base64PNG).toMatchImageSnapshot(imageSnapshotOptions);
});

test('Typed text with escaped emoticons will replace to fully escaped emoticon', async () => {
const { driver, pageObjects } = await setupWebDriver({
props: {
styleOptions: { emojiSet: { '):': '☹️' } }
}
});

await driver.wait(uiConnected(), timeouts.directLine);

await pageObjects.typeInSendBox('function enabled(\\): boolean { return true; }');

const base64PNG = await driver.takeScreenshot();

expect(base64PNG).toMatchImageSnapshot(imageSnapshotOptions);
});
64 changes: 64 additions & 0 deletions __tests__/hooks/useReplaceEmoticon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { timeouts } from '../constants.json';

// selenium-webdriver API doc:
// https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html

jest.setTimeout(timeouts.test);

test('useReplaceEmoticon should NOT convert text with emoticon to text with emoji when disabled', async () => {
const { pageObjects } = await setupWebDriver();

await expect(pageObjects.runHook('useReplaceEmoticon', [], fn => fn('hi :) <3 :( :sheep:'))).resolves.toBe(
`hi :) <3 :( :sheep:`
);
});

test('useReplaceEmoticon should convert text with emoticon to text with emoji when enabled', async () => {
const { pageObjects } = await setupWebDriver({
props: {
styleOptions: {
emojiSet: true
}
}
});

await expect(pageObjects.runHook('useReplaceEmoticon', [], fn => fn('hi :( <3 :sheep:'))).resolves.toBe(
`hi ☹️ ❀️ :sheep:`
);
});

test('useReplaceEmoticon should convert text with custom emoticon to text with custom emoji', async () => {
const { pageObjects } = await setupWebDriver({
props: {
styleOptions: {
emojiSet: { ':sheep:': 'πŸ‘' }
}
}
});

await expect(pageObjects.runHook('useReplaceEmoticon', [], fn => fn('<3 :( :sheep:'))).resolves.toBe(`<3 :( πŸ‘`);
});

test('replaceEmoticon, when using a sorted custom emojiSet, should convert :o but :o) will not be converted', async () => {
const { pageObjects } = await setupWebDriver({
props: {
styleOptions: {
emojiSet: { ':o': '😲', ':o)': '🀑' }
}
}
});

await expect(pageObjects.runHook('useReplaceEmoticon', [], fn => fn(':o)'))).resolves.toBe(`😲)`);
});

test('replaceEmoticon, when using a unsorted custom emojiSet, should convert :o but :o) will not be converted', async () => {
const { pageObjects } = await setupWebDriver({
props: {
styleOptions: {
emojiSet: { ':o)': '🀑', ':o': '😲' }
}
}
});

await expect(pageObjects.runHook('useReplaceEmoticon', [], fn => fn(':o)'))).resolves.toBe(`😲)`);
});
35 changes: 35 additions & 0 deletions __tests__/html/emoji.disabled.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<script crossorigin="anonymous" src="/__dist__/testharness.js"></script>
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
</head>
<body>
<div id="webchat"></div>
<script type="text/babel" data-presets="env,stage-3,react">
const { conditions, createStore, expect, host, pageObjects, timeouts, token } = window.WebChatTest;

(async function() {
window.WebChat.renderWebChat(
{
directLine: window.WebChat.createDirectLine({ token: await token.fetchDirectLineToken() }),
store: createStore()
// styleOptions: { emojiAutocorrect: false } // (default)
corinagum marked this conversation as resolved.
Show resolved Hide resolved
},
document.getElementById('webchat')
);

await pageObjects.wait(conditions.uiConnected(), timeouts.directLine);

await pageObjects.typeInSendBox(':) (: :-| :D :-p :-o <3 :(');

await host.snapshot();
await host.done();
})().catch(async err => {
console.error(err);

await host.error(err);
});
</script>
</body>
</html>
7 changes: 7 additions & 0 deletions __tests__/html/emoji.disabled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* @jest-environment ./__tests__/html/__jest__/WebChatEnvironment.js
*/

describe('Emoji should be disabled if emoji style props have not been changed', () => {
test('', () => runHTMLTest('emoji.disabled.html'));
});
40 changes: 40 additions & 0 deletions __tests__/html/emoji.enabled.customMap.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<script crossorigin="anonymous" src="/__dist__/testharness.js"></script>
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
</head>
<body>
<div id="webchat"></div>
<script type="text/babel" data-presets="env,stage-3,react">
const { conditions, createStore, expect, host, pageObjects, timeouts, token } = window.WebChatTest;

(async function() {
window.WebChat.renderWebChat(
{
directLine: window.WebChat.createDirectLine({ token: await token.fetchDirectLineToken() }),
store: createStore(),
styleOptions: {
emojiSet: {
':)': '😊'
}
// emojiSet: true
}
},
document.getElementById('webchat')
);

await pageObjects.wait(conditions.uiConnected(), timeouts.directLine);

await pageObjects.typeInSendBox(':) (: :-| :D :-p :-o <3 :(');

await host.snapshot();
await host.done();
})().catch(async err => {
console.error(err);

await host.error(err);
});
</script>
</body>
</html>
7 changes: 7 additions & 0 deletions __tests__/html/emoji.enabled.customMap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* @jest-environment ./__tests__/html/__jest__/WebChatEnvironment.js
*/

describe('Correct emoji should display if custom emojiSet has been set', () => {
test('', () => runHTMLTest('emoji.enabled.customMap.html'));
});
35 changes: 35 additions & 0 deletions __tests__/html/emoji.enabled.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<script crossorigin="anonymous" src="/__dist__/testharness.js"></script>
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
</head>
<body>
<div id="webchat"></div>
<script type="text/babel" data-presets="env,stage-3,react">
const { conditions, createStore, expect, host, pageObjects, timeouts, token } = window.WebChatTest;

(async function() {
window.WebChat.renderWebChat(
{
directLine: window.WebChat.createDirectLine({ token: await token.fetchDirectLineToken() }),
store: createStore(),
styleOptions: { emojiSet: true }
},
document.getElementById('webchat')
);

await pageObjects.wait(conditions.uiConnected(), timeouts.directLine);

await pageObjects.typeInSendBox(':) (: :-| :D :-p :-o <3 :(');

await host.snapshot();
await host.done();
})().catch(async err => {
console.error(err);

await host.error(err);
});
</script>
</body>
</html>
7 changes: 7 additions & 0 deletions __tests__/html/emoji.enabled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* @jest-environment ./__tests__/html/__jest__/WebChatEnvironment.js
*/

describe('Correct emoji should display if emojiSet has been set to true', () => {
test('', () => runHTMLTest('emoji.enabled.html'));
});
11 changes: 11 additions & 0 deletions docs/HOOKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ Following is the list of hooks supported by Web Chat API.
- [`useRenderMarkdownAsHTML`](#userendermarkdownashtml)
- [`useRenderToast`](#userendertoast)
- [`useRenderTypingIndicator`](#userendertypingindicator)
- [`useReplaceEmoticon`](#useReplaceEmoticon)
- [`useScrollTo`](#usescrollto)
- [`useScrollToEnd`](#usescrolltoend)
- [`useSendBoxValue`](#usesendboxvalue)
Expand Down Expand Up @@ -741,6 +742,16 @@ This function is for rendering typing indicator for all participants of the conv
- `typing` lists participants who did not explicitly stopped typing. This list is a superset of `activeTyping`.
- `visible` indicates whether typing indicator should be shown in normal case. This is based on participants in `activeTyping` and their `role` (role not equal to `"user"`).

## `useReplaceEmoticon`

<!-- prettier-ignore-start -->
```js
useEmoticonReplacer() => (valueWithEmoticon: string) => string
```
<!-- prettier-ignore-end -->

This function is for enabling autocorrect from emoticon to emoji text in the SendBox.The caller passes the current value of the text box.

## `useScrollTo`

<!-- prettier-ignore-start -->
Expand Down
Loading