Skip to content

Commit d0aa541

Browse files
compulimms-jb
andauthored
[P0] Send attachment on send (#5123)
* Add style option to allow for file type narrowing * Rename and add multi upload prop * Add e2e test to validate <input> * Resolve comments * Revert to true * Add style option * Add hook * Pass text through to activity * Add file state to context * Pass text to sendFiles * Finalize upload and sendbox action * Only send text if defined * Add changelog entry * Add e2e test * Bump version * Switch to check icon * Initialize as undefined * Store the upload button ref in context and clear after sending * Fix types and pass ref directly to send box * Update icon and sizing * Smaller icon * Updated send box icons per design * Use Redux and sendMessage, instead of useState and sendFile * Reformat * Update DOCTYPE * Add deprecation warning * Rename to attach in sendAttachmentOn * Fix test * Remove deprecation note * Clean up * Expose updated useSendMessage hook * Add tests * Add tests * Delete test * Add test for speech * Comments on ignoreErrors * Revert icon changes * Fix test * Add telemetry * Migrate test * Use older icon * Fix flakiness * Revert screenshots * Remove uploadButtonRef * Dispatch should return void * Sort imports * Sort imports * Remove uploadButtonRef * Sort imports * Sort imports * Add test * Add screenshots * Use exact version for production dependencies * Sort imports * Update comment * Update deps * Remove empty line * Update exact version * Revert unnecessary changes * Sort imports * Simplify typing of SendBoxAttachment * Clean up * Remove unused file * Sort * Revert * Update useSendMessage * Update content type * Fix flaky * Fix tests * Fix flakiness * Add sendAttachmentOn * Fix test * Fix test * Fix test * Revert behavior * Backward compatibility * Add custom thumbnail * Add notes * Revert * Revert API useSendFiles * Add typings * Remove satisfies --------- Co-authored-by: Jordan Berger <[email protected]>
1 parent c2246e4 commit d0aa541

File tree

134 files changed

+2058
-656
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

134 files changed

+2058
-656
lines changed

CHANGELOG.md

+8

__tests__/attachmentMiddleware.js

+30-26
Original file line numberDiff line numberDiff line change
@@ -17,36 +17,40 @@ jest.setTimeout(timeouts.test);
1717
test('file upload should show thumbnail and file name', async () => {
1818
const { driver, pageObjects } = await setupWebDriver({
1919
props: {
20-
attachmentMiddleware: () => next => ({
21-
activity = {},
22-
activity: { from: { role } } = {},
23-
attachment,
24-
attachment: { contentType, thumbnailUrl } = {}
25-
}) => {
26-
if (role === 'user' && /^image\//u.test(contentType) && thumbnailUrl) {
27-
const patchedAttachment = Object.assign({}, attachment, {
28-
contentType: 'application/octet-stream',
29-
thumbnailUrl: undefined
30-
});
20+
attachmentMiddleware:
21+
() =>
22+
next =>
23+
({
24+
activity = {},
25+
activity: { from: { role } } = {},
26+
attachment,
27+
attachment: { contentType, thumbnailUrl } = {}
28+
}) => {
29+
if (role === 'user' && /^image\//u.test(contentType) && thumbnailUrl) {
30+
const patchedAttachment = Object.assign({}, attachment, {
31+
contentType: 'application/octet-stream',
32+
thumbnailUrl: undefined
33+
});
3134

32-
const patchedAttachments = activity.attachments.map(target =>
33-
target === attachment ? patchedAttachment : target
34-
);
35+
const patchedAttachments = activity.attachments.map(target =>
36+
target === attachment ? patchedAttachment : target
37+
);
3538

36-
const patchedActivity = Object.assign({}, activity, {
37-
attachments: patchedAttachments
38-
});
39+
const patchedActivity = Object.assign({}, activity, {
40+
attachments: patchedAttachments
41+
});
3942

40-
return React.createElement(
41-
React.Fragment,
42-
{},
43-
next({ activity, attachment }),
44-
next({ activity: patchedActivity, attachment: patchedAttachment })
45-
);
46-
}
43+
return React.createElement(
44+
React.Fragment,
45+
{},
46+
next({ activity, attachment }),
47+
next({ activity: patchedActivity, attachment: patchedAttachment })
48+
);
49+
}
4750

48-
return next({ activity, attachment });
49-
}
51+
return next({ activity, attachment });
52+
},
53+
styleOptions: { sendAttachmentOn: 'attach' }
5054
},
5155
// TODO: [P3] Offline bot did not reply with a downloadable attachment, we need to use production bot
5256
useProductionBot: true

__tests__/fileAttachment.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ test('show ZIP files with contentUrl', async () => {
3939
};
4040

4141
return patchedDirectLine;
42-
}
42+
},
43+
props: { styleOptions: { sendAttachmentOn: 'attach' } }
4344
});
4445

4546
await driver.wait(uiConnected(), timeouts.directLine);
@@ -112,7 +113,8 @@ test('show ZIP files without contentUrl', async () => {
112113
};
113114

114115
return patchedDirectLine;
115-
}
116+
},
117+
props: { styleOptions: { sendAttachmentOn: 'attach' } }
116118
});
117119

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

__tests__/hooks/useSendFiles.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ test('calling sendFile should send files', async () => {
2727
sendFiles([blob1, blob2]);
2828
});
2929

30-
await driver.wait(minNumActivitiesShown(2), timeouts.directLine);
30+
await driver.wait(minNumActivitiesShown(3), timeouts.directLine);
3131
await driver.wait(allOutgoingActivitiesSent(), timeouts.directLine);
3232

3333
const base64PNG = await driver.takeScreenshot();

__tests__/html/attachment.allowedFileTypes.html

+3-3
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@
2626
);
2727

2828
await pageConditions.uiConnected();
29-
29+
3030
// Validate the <input> element has the correct file types
31-
const uploadButton = pageElements.uploadButton()
31+
const uploadButton = pageElements.uploadButton();
32+
3233
expect(uploadButton).toHaveProperty('accept', 'image/*');
3334
expect(uploadButton).toHaveProperty('multiple', false);
34-
3535
});
3636
</script>
3737
</body>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
*:focus {
2+
background-color: yellow !important;
3+
outline: dashed 2px Red !important;
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<!doctype html>
2+
<html lang="en-US">
3+
<head>
4+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
5+
<link href="focus-indicator.css" rel="stylesheet" type="text/css" />
6+
<script crossorigin="anonymous" src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
7+
<script crossorigin="anonymous" src="/test-harness.js"></script>
8+
<script crossorigin="anonymous" src="/test-page-object.js"></script>
9+
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
10+
</head>
11+
<body>
12+
<main id="webchat"></main>
13+
<script>
14+
run(
15+
async function () {
16+
const directLine = WebChat.createDirectLine({ token: await testHelpers.token.fetchDirectLineToken() });
17+
const store = testHelpers.createStore();
18+
19+
WebChat.renderWebChat(
20+
{ activityMiddleware: testHelpers.createRunHookActivityMiddleware(), directLine, store },
21+
document.getElementById('webchat')
22+
);
23+
24+
await pageConditions.uiConnected();
25+
26+
// SETUP: An empty file blob.
27+
const blob = new File([new ArrayBuffer(1024)], 'empty.zip', { type: 'image/jpeg' });
28+
29+
// WHEN: Call useSendMessage hook to send a file a custom thumbnail.
30+
await pageObjects.runHook(({ useSendMessage }) =>
31+
useSendMessage()(undefined, undefined, { attachments: [{ blob }] })
32+
);
33+
34+
// THEN: It should send the file.
35+
await pageConditions.allOutgoingActivitiesSent();
36+
await pageConditions.numActivitiesShown(3);
37+
await host.snapshot();
38+
},
39+
{ ignoreErrors: 'The source image could not be decoded.' }
40+
);
41+
</script>
42+
</body>
43+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */
2+
3+
describe('useSendMessage hook', () => {
4+
test('should send zip file as image', () => runHTML('sendAttachmentOn/invalidImage'));
5+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<!doctype html>
2+
<html lang="en-US">
3+
<head>
4+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
5+
<link href="focus-indicator.css" rel="stylesheet" type="text/css" />
6+
<script crossorigin="anonymous" src="/test-harness.js"></script>
7+
<script crossorigin="anonymous" src="/test-page-object.js"></script>
8+
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
9+
</head>
10+
<body>
11+
<main id="webchat"></main>
12+
<script>
13+
run(async function () {
14+
const directLine = WebChat.createDirectLine({ token: await testHelpers.token.fetchDirectLineToken() });
15+
const store = testHelpers.createStore();
16+
17+
WebChat.renderWebChat(
18+
{ directLine, store, styleOptions: { sendAttachmentOn: 'attach' } },
19+
document.getElementById('webchat')
20+
);
21+
22+
await pageConditions.uiConnected();
23+
24+
// WHEN: Upload button is clicked and a JPEG file is selected.
25+
await host.upload(pageElements.uploadButton(), 'seaofthieves.jpg');
26+
27+
// THEN: A thumbnail with the bot reply should show.
28+
await pageConditions.allOutgoingActivitiesSent();
29+
await pageConditions.numActivitiesShown(2);
30+
await host.snapshot();
31+
});
32+
</script>
33+
</body>
34+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */
2+
3+
describe('with "sendAttachmentOn" of "attach"', () => {
4+
test('should send attachments when the file is attached', () => runHTML('sendAttachmentOn/onAttach'));
5+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<!doctype html>
2+
<html lang="en-US">
3+
<head>
4+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
5+
<link href="focus-indicator.css" rel="stylesheet" type="text/css" />
6+
<script crossorigin="anonymous" src="/test-harness.js"></script>
7+
<script crossorigin="anonymous" src="/test-page-object.js"></script>
8+
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
9+
</head>
10+
<body>
11+
<main id="webchat"></main>
12+
<script>
13+
run(async function () {
14+
const directLine = WebChat.createDirectLine({ token: await testHelpers.token.fetchDirectLineToken() });
15+
const store = testHelpers.createStore();
16+
17+
WebChat.renderWebChat(
18+
{ directLine, store, styleOptions: { sendAttachmentOn: 'send' } },
19+
document.getElementById('webchat')
20+
);
21+
22+
await pageConditions.uiConnected();
23+
24+
// WHEN: Upload button is clicked and a JPEG file is selected.
25+
await host.upload(pageElements.uploadButton(), 'seaofthieves.jpg');
26+
27+
// THEN: A checkmark should show next to the "upload button".
28+
await host.snapshot();
29+
30+
// WHEN: Send button is clicked.
31+
await host.click(pageElements.sendButton());
32+
33+
// THEN: A thumbnail with the bot reply should show.
34+
await pageConditions.allOutgoingActivitiesSent();
35+
await pageConditions.numActivitiesShown(2);
36+
await host.snapshot();
37+
});
38+
</script>
39+
</body>
40+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */
2+
3+
describe('with "sendAttachmentOn" of "send"', () => {
4+
test('should send attachments when the send button is clicked', () => runHTML('sendAttachmentOn/onSend'));
5+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<!doctype html>
2+
<html lang="en-US">
3+
<head>
4+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
5+
<link href="focus-indicator.css" rel="stylesheet" type="text/css" />
6+
<script crossorigin="anonymous" src="/test-harness.js"></script>
7+
<script crossorigin="anonymous" src="/test-page-object.js"></script>
8+
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
9+
</head>
10+
<body>
11+
<main id="webchat"></main>
12+
<script>
13+
run(async function () {
14+
const directLine = WebChat.createDirectLine({ token: await testHelpers.token.fetchDirectLineToken() });
15+
const store = testHelpers.createStore();
16+
17+
WebChat.renderWebChat({ directLine, store }, document.getElementById('webchat'));
18+
19+
await pageConditions.uiConnected();
20+
21+
// WHEN: Upload button is clicked and a JPEG file is selected.
22+
await host.upload(pageElements.uploadButton(), 'seaofthieves.jpg');
23+
24+
// THEN: A checkmark should show next to the "upload button".
25+
await host.snapshot();
26+
27+
// WHEN: Send button is clicked.
28+
await host.click(pageElements.sendButton());
29+
30+
// THEN: A thumbnail with the bot reply should show.
31+
await pageConditions.allOutgoingActivitiesSent();
32+
await pageConditions.numActivitiesShown(2);
33+
await host.snapshot();
34+
});
35+
</script>
36+
</body>
37+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */
2+
3+
describe('with "sendAttachmentOn" unset', () => {
4+
test('should send attachments when the send button is clicked', () => runHTML('sendAttachmentOn/simple'));
5+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<!doctype html>
2+
<html lang="en-US">
3+
<head>
4+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
5+
<link href="focus-indicator.css" rel="stylesheet" type="text/css" />
6+
<script crossorigin="anonymous" src="/test-harness.js"></script>
7+
<script crossorigin="anonymous" src="/test-page-object.js"></script>
8+
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
9+
</head>
10+
<body>
11+
<main id="webchat"></main>
12+
<script>
13+
run(async function () {
14+
const directLine = WebChat.createDirectLine({ token: await testHelpers.token.fetchDirectLineToken() });
15+
const store = testHelpers.createStore();
16+
17+
WebChat.renderWebChat({ directLine, store }, document.getElementById('webchat'));
18+
19+
await pageConditions.uiConnected();
20+
21+
pageElements.sendBoxTextBox().focus();
22+
23+
// WHEN: SHIFT TAB key is pressed.
24+
await host.sendShiftTab();
25+
26+
// THEN: Should focus on the upload button.
27+
await host.snapshot();
28+
29+
// WHEN: Upload button is clicked and a JPEG file is selected.
30+
// Notes: WebDriver does not support testing against the native file chooser dialog.
31+
await host.upload(pageElements.uploadButton(), 'seaofthieves.jpg');
32+
33+
// THEN: A checkmark should show next to the "upload button".
34+
await host.snapshot();
35+
36+
// WHEN: TAB key is pressed.
37+
await host.sendTab();
38+
39+
// THEN: Should focus on the send button.
40+
await host.snapshot();
41+
42+
// WHEN: SPACEBAR key is pressed.
43+
await host.sendKeys(' ');
44+
45+
// THEN: A thumbnail with the bot reply should show.
46+
await pageConditions.allOutgoingActivitiesSent();
47+
await pageConditions.numActivitiesShown(2);
48+
await host.snapshot();
49+
});
50+
</script>
51+
</body>
52+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */
2+
3+
describe('with "sendAttachmentOn" unset and use keyboard for the flow', () => {
4+
test('should send attachments when the send button is clicked', () => runHTML('sendAttachmentOn/simple.keyboardOnly'));
5+
});

0 commit comments

Comments
 (0)