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

test: Custom message unit test setup (duplicate) #398

Closed
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
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.
1 change: 1 addition & 0 deletions __visual_tests__/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ export const WidgetComponentIds = {
INPUT: '.sendbird-input__input',
CHIPS_CONTAINER: '.sendbird-form-chip__container',
FORM: '#aichatbot-widget-form',
MARKDOWN: '.widget-markdown',
};
9 changes: 8 additions & 1 deletion __visual_tests__/utils/testUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@ import { expect, Page } from '@playwright/test';

import { getWidgetSessionCache } from './localStorageUtils';
import { deleteChannel, deleteUser } from './requestUtils';
import { AppId, BotId, WidgetComponentIds } from '../const';
import { AppId, BotId, TestUrl, WidgetComponentIds } from '../const';

export async function beforeEach(page: Page, url = TestUrl) {
await page.goto(url);

const widgetWindow = page.locator(WidgetComponentIds.WIDGET_BUTTON);
await widgetWindow.waitFor({ state: 'visible' });
}

export async function assertScreenshot(page: Page, screenshotName: string, browserName: string) {
const name = `${screenshotName}.${browserName}.${process.platform}.png`; // Include the browser and OS architecture info in the filename
Expand Down
78 changes: 70 additions & 8 deletions __visual_tests__/workflow-tests.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { test } from '@playwright/test';

import { TestUrl, WidgetComponentIds } from './const';
import { assertScreenshot, clickNthChip, deleteTestResources, loadWidget, sendTextMessage } from './utils/testUtils';

test.beforeEach(async ({ page }) => {
await page.goto(TestUrl);

const widgetWindow = page.locator(WidgetComponentIds.WIDGET_BUTTON);
await widgetWindow.waitFor({ state: 'visible' });
});
import {
assertScreenshot,
beforeEach,
clickNthChip,
deleteTestResources,
loadWidget,
sendTextMessage,
} from './utils/testUtils';

test.afterEach(async ({ page }) => {
await deleteTestResources(page);
Expand All @@ -29,6 +29,7 @@ test.afterEach(async ({ page }) => {
* 4. Submit form with valid values.
*/
test('100', async ({ page, browserName }) => {
await beforeEach(page);
await loadWidget(page);

// 1
Expand Down Expand Up @@ -69,6 +70,7 @@ test('100', async ({ page, browserName }) => {
* 1. Send the trigger message: "Tell me about one cat breed"
*/
test('101', async ({ page, browserName }) => {
await beforeEach(page);
await loadWidget(page);
// 1
await sendTextMessage(page, 'Tell me about one cat breed', 2000);
Expand All @@ -82,6 +84,7 @@ test('101', async ({ page, browserName }) => {
* 1. Send the trigger message: "Give me a travel agency poster"
*/
test('102', async ({ page, browserName }) => {
await beforeEach(page);
await loadWidget(page);
// 1
await sendTextMessage(page, 'Give me a travel agency poster', 5000);
Expand All @@ -100,6 +103,7 @@ test('102', async ({ page, browserName }) => {
* 6. Click "Link to workflow: form message"
*/
test('103', async ({ page, browserName }) => {
await beforeEach(page);
await loadWidget(page);
// 1
await sendTextMessage(page, 'Suggested replies', 2000);
Expand Down Expand Up @@ -137,3 +141,61 @@ test('103', async ({ page, browserName }) => {
await page.waitForTimeout(2000);
await assertScreenshot(page, '103-6', browserName);
});

/**
* 104
* Workflow - Markdown response
* Steps:
* 1. Send the trigger message: "give me a markdown message"
* 2. Click "Part 2"
* 3. Click "Back"
* 4. Click "Part 3"
* 5. Click "Back"
* 6. Click "Part 4"
*/
test('104', async ({ page, browserName }) => {
await beforeEach(page, TestUrl + '&suggested_replies_direction=horizontal');
await loadWidget(page);
// 1
await sendTextMessage(page, 'give me a markdown message', 2000);

// Check if the fallback component is visible
const fallback = page.locator(WidgetComponentIds.MARKDOWN);
await fallback.first().waitFor({ state: 'visible' });
let options = page.locator(WidgetComponentIds.SUGGESTED_REPLIES_OPTIONS);
await options.nth(3).waitFor({ state: 'visible' });
await assertScreenshot(page, '104-1', browserName);

// 2
await options.nth(0).click();
options = page.locator(WidgetComponentIds.SUGGESTED_REPLIES_OPTIONS);
await options.nth(0).waitFor({ state: 'visible' }); // Wait for go back button to show
await assertScreenshot(page, '104-2', browserName);
await options.nth(0).click(); // Go back
options = page.locator(WidgetComponentIds.SUGGESTED_REPLIES_OPTIONS);
await options.nth(3).waitFor({ state: 'visible' });

// 3
await options.nth(1).click();
options = page.locator(WidgetComponentIds.SUGGESTED_REPLIES_OPTIONS);
await options.nth(0).waitFor({ state: 'visible' }); // Wait for go back button to show
await assertScreenshot(page, '104-3', browserName);
await options.nth(0).click(); // Go back
options = page.locator(WidgetComponentIds.SUGGESTED_REPLIES_OPTIONS);
await options.nth(3).waitFor({ state: 'visible' });

// 4
await options.nth(2).click();
options = page.locator(WidgetComponentIds.SUGGESTED_REPLIES_OPTIONS);
await options.nth(0).waitFor({ state: 'visible' }); // Wait for go back button to show
await assertScreenshot(page, '104-4', browserName);
await options.nth(0).click(); // Go back
options = page.locator(WidgetComponentIds.SUGGESTED_REPLIES_OPTIONS);
await options.nth(3).waitFor({ state: 'visible' });

// 5
await options.nth(3).click();
options = page.locator(WidgetComponentIds.SUGGESTED_REPLIES_OPTIONS);
await options.nth(0).waitFor({ state: 'visible' }); // Wait for go back button to show
await assertScreenshot(page, '104-5', browserName);
});
157 changes: 157 additions & 0 deletions __visual_unit_tests__/markdown-message-tests.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import { test, expect } from '@playwright/test';

/**
* markdown message
*/
test(`100`, async ({ page }) => {
const message =
'# A demo of `react-markdown`\n' +
'\n' +
'`react-markdown` is a markdown component for React.\n' +
'\n' +
'👉 Changes are re-rendered as you type.\n' +
'\n' +
'👈 Try writing some markdown on the left.\n' +
'\n' +
'## Overview\n' +
'\n' +
'* Follows [CommonMark](https://commonmark.org)\n' +
'* Optionally follows [GitHub Flavored Markdown](https://github.github.com/gfm/)\n' +
'* Renders actual React elements instead of using `dangerouslySetInnerHTML`\n' +
"* Lets you define your own components (to render `MyHeading` instead of `'h1'`)\n" +
'* Has a lot of plugins\n' +
'\n' +
'## Contents\n' +
'\n' +
'Here is an example of a plugin in action\n' +
'([`remark-toc`](https://github.com/remarkjs/remark-toc)).\n' +
'**This section is replaced by an actual table of contents**.\n' +
'\n' +
'## Syntax highlighting\n' +
'\n' +
'Here is an example of a plugin to highlight code:\n' +
'[`rehype-highlight`](https://github.com/rehypejs/rehype-highlight).\n' +
'\n' +
'```js\n' +
"import React from 'react'\n" +
"import ReactDOM from 'react-dom'\n" +
"import Markdown from 'react-markdown'\n" +
"import rehypeHighlight from 'rehype-highlight'\n" +
'\n' +
'const markdown = `\n' +
'# Your markdown here\n' +
'`\n' +
'\n' +
'ReactDOM.render(\n' +
' <Markdown rehypePlugins={[rehypeHighlight]}>{markdown}</Markdown>,\n' +
" document.querySelector('#content')\n" +
')\n' +
'```\n' +
'\n' +
'Pretty neat, eh?\n' +
'\n' +
'## GitHub flavored markdown (GFM)\n' +
'\n' +
'For GFM, you can *also* use a plugin:\n' +
'[`remark-gfm`](https://github.com/remarkjs/react-markdown#use).\n' +
'It adds support for GitHub-specific extensions to the language:\n' +
'tables, strikethrough, tasklists, and literal URLs.\n' +
'\n' +
'These features **do not work by default**.\n' +
'👆 Use the toggle above to add the plugin.\n' +
'\n' +
'| Feature | Support |\n' +
'| ---------: | :------------------- |\n' +
'| CommonMark | 100% |\n' +
'| GFM | 100% w/ `remark-gfm` |\n' +
'\n' +
'~~strikethrough~~\n' +
'\n' +
'* [ ] task list\n' +
'* [x] checked item\n' +
'\n' +
'https://example.com\n' +
'\n' +
'## HTML in markdown\n' +
'\n' +
'⚠️ HTML in markdown is quite unsafe, but if you want to support it, you can\n' +
'use [`rehype-raw`](https://github.com/rehypejs/rehype-raw).\n' +
'You should probably combine it with\n' +
'[`rehype-sanitize`](https://github.com/rehypejs/rehype-sanitize).\n' +
'\n' +
'<blockquote>\n' +
' 👆 Use the toggle above to add the plugin.\n' +
'</blockquote>\n' +
'\n' +
'## Components\n' +
'\n' +
'You can pass components to change things:\n' +
'\n' +
'```js\n' +
"import React from 'react'\n" +
"import ReactDOM from 'react-dom'\n" +
"import Markdown from 'react-markdown'\n" +
"import MyFancyRule from './components/my-fancy-rule.js'\n" +
'\n' +
'const markdown = `\n' +
'# Your markdown here\n' +
'`\n' +
'\n' +
'ReactDOM.render(\n' +
' <Markdown\n' +
' components={{\n' +
' // Use h2s instead of h1s\n' +
" h1: 'h2',\n" +
' // Use a component instead of hrs\n' +
' hr(props) {\n' +
' const {node, ...rest} = props\n' +
' return <MyFancyRule {...rest} />\n' +
' }\n' +
' }}\n' +
' >\n' +
' {markdown}\n' +
' </Markdown>,\n' +
" document.querySelector('#content')\n" +
')\n' +
'```\n' +
'\n' +
'## More info?\n' +
'\n' +
'Much more info is available in the\n' +
'[readme on GitHub](https://github.com/remarkjs/react-markdown)!\n' +
'\n' +
'***\n' +
'\n' +
'A component by [Espen Hovlandsdal](https://espen.codes/)';

// For logging.
// page.on('console', msg => console.log('BROWSER LOG:', msg.text()));

await page.goto('http://localhost:5273/', { waitUntil: 'networkidle' });

// Ensure the root is loaded
const appRoot = page.locator('[data-testid="snapshot-unit-test-app-root"]');
await expect(appRoot).toBeVisible();

// Debug: Ensure CustomMessage is not already there
const preRenderCheck = await page.isVisible('[data-testid="wowwow"]');
expect(preRenderCheck).toBeFalsy(); // Assert false

// Simulate setting a message through a mocked action
await page.evaluate((msg) => {
if (typeof (window as any).setMessage === 'function') {
(window as any).setMessage(msg);
} else {
throw new Error('setMessage not exposed!');
}
}, message);

await page.waitForSelector('[data-testid="wowwow"]', {
state: 'visible',
timeout: 5000, // Give React time to re-render
});

const customMessage = page.locator('[data-testid="wowwow"]');
await customMessage.isVisible();
await expect(customMessage).toHaveScreenshot({ omitBackground: false });
});
24 changes: 24 additions & 0 deletions apps/visual-test/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
13 changes: 13 additions & 0 deletions apps/visual-test/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Widget snapshot unit test</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
26 changes: 26 additions & 0 deletions apps/visual-test/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "@sendbird/visual-unit-test",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@eslint/js": "^9.15.0",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.4",
"globals": "^15.12.0",
"typescript": "~5.6.2",
"typescript-eslint": "^8.15.0",
"vite": "^6.0.1"
}
}
32 changes: 32 additions & 0 deletions apps/visual-test/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { UserMessage } from '@sendbird/chat/message';
import { useEffect, useState } from 'react';

import { getMockedUserMessage } from './utils.ts';
import CustomMessage from '../../../src/components/CustomMessage.tsx';

const App = () => {
const [message, setMessage] = useState<UserMessage | null>(getMockedUserMessage());

// Expose setMessage for testing purposes
useEffect(() => {
if (import.meta.env.MODE === 'test') {
(window as any).setMessage = (msg: string) => {
setMessage(getMockedUserMessage(msg));
};
}
}, []);

return (
<div data-testid="snapshot-unit-test-app-root">
<h1>Test</h1>
{message && (
<div data-testid="wowwow">
{/*FIXME: Note that below component must be wrapped with ChatProvider.*/}
<CustomMessage message={message} activeSpinnerId={-1} />
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please note that CustomMessage must be wrapped with ChatProvider in order for this app to work.

</div>
)}
</div>
);
};

export default App;
Loading
Loading