Skip to content

Commit 42dbdcd

Browse files
committed
Added unit test for markdown message
1 parent ad9576f commit 42dbdcd

18 files changed

+3515
-5159
lines changed
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import { test, expect } from '@playwright/test';
2+
3+
/**
4+
* markdown message
5+
*/
6+
test(`100`, async ({ page }) => {
7+
const message =
8+
'# A demo of `react-markdown`\n' +
9+
'\n' +
10+
'`react-markdown` is a markdown component for React.\n' +
11+
'\n' +
12+
'👉 Changes are re-rendered as you type.\n' +
13+
'\n' +
14+
'👈 Try writing some markdown on the left.\n' +
15+
'\n' +
16+
'## Overview\n' +
17+
'\n' +
18+
'* Follows [CommonMark](https://commonmark.org)\n' +
19+
'* Optionally follows [GitHub Flavored Markdown](https://github.github.com/gfm/)\n' +
20+
'* Renders actual React elements instead of using `dangerouslySetInnerHTML`\n' +
21+
"* Lets you define your own components (to render `MyHeading` instead of `'h1'`)\n" +
22+
'* Has a lot of plugins\n' +
23+
'\n' +
24+
'## Contents\n' +
25+
'\n' +
26+
'Here is an example of a plugin in action\n' +
27+
'([`remark-toc`](https://github.com/remarkjs/remark-toc)).\n' +
28+
'**This section is replaced by an actual table of contents**.\n' +
29+
'\n' +
30+
'## Syntax highlighting\n' +
31+
'\n' +
32+
'Here is an example of a plugin to highlight code:\n' +
33+
'[`rehype-highlight`](https://github.com/rehypejs/rehype-highlight).\n' +
34+
'\n' +
35+
'```js\n' +
36+
"import React from 'react'\n" +
37+
"import ReactDOM from 'react-dom'\n" +
38+
"import Markdown from 'react-markdown'\n" +
39+
"import rehypeHighlight from 'rehype-highlight'\n" +
40+
'\n' +
41+
'const markdown = `\n' +
42+
'# Your markdown here\n' +
43+
'`\n' +
44+
'\n' +
45+
'ReactDOM.render(\n' +
46+
' <Markdown rehypePlugins={[rehypeHighlight]}>{markdown}</Markdown>,\n' +
47+
" document.querySelector('#content')\n" +
48+
')\n' +
49+
'```\n' +
50+
'\n' +
51+
'Pretty neat, eh?\n' +
52+
'\n' +
53+
'## GitHub flavored markdown (GFM)\n' +
54+
'\n' +
55+
'For GFM, you can *also* use a plugin:\n' +
56+
'[`remark-gfm`](https://github.com/remarkjs/react-markdown#use).\n' +
57+
'It adds support for GitHub-specific extensions to the language:\n' +
58+
'tables, strikethrough, tasklists, and literal URLs.\n' +
59+
'\n' +
60+
'These features **do not work by default**.\n' +
61+
'👆 Use the toggle above to add the plugin.\n' +
62+
'\n' +
63+
'| Feature | Support |\n' +
64+
'| ---------: | :------------------- |\n' +
65+
'| CommonMark | 100% |\n' +
66+
'| GFM | 100% w/ `remark-gfm` |\n' +
67+
'\n' +
68+
'~~strikethrough~~\n' +
69+
'\n' +
70+
'* [ ] task list\n' +
71+
'* [x] checked item\n' +
72+
'\n' +
73+
'https://example.com\n' +
74+
'\n' +
75+
'## HTML in markdown\n' +
76+
'\n' +
77+
'⚠️ HTML in markdown is quite unsafe, but if you want to support it, you can\n' +
78+
'use [`rehype-raw`](https://github.com/rehypejs/rehype-raw).\n' +
79+
'You should probably combine it with\n' +
80+
'[`rehype-sanitize`](https://github.com/rehypejs/rehype-sanitize).\n' +
81+
'\n' +
82+
'<blockquote>\n' +
83+
' 👆 Use the toggle above to add the plugin.\n' +
84+
'</blockquote>\n' +
85+
'\n' +
86+
'## Components\n' +
87+
'\n' +
88+
'You can pass components to change things:\n' +
89+
'\n' +
90+
'```js\n' +
91+
"import React from 'react'\n" +
92+
"import ReactDOM from 'react-dom'\n" +
93+
"import Markdown from 'react-markdown'\n" +
94+
"import MyFancyRule from './components/my-fancy-rule.js'\n" +
95+
'\n' +
96+
'const markdown = `\n' +
97+
'# Your markdown here\n' +
98+
'`\n' +
99+
'\n' +
100+
'ReactDOM.render(\n' +
101+
' <Markdown\n' +
102+
' components={{\n' +
103+
' // Use h2s instead of h1s\n' +
104+
" h1: 'h2',\n" +
105+
' // Use a component instead of hrs\n' +
106+
' hr(props) {\n' +
107+
' const {node, ...rest} = props\n' +
108+
' return <MyFancyRule {...rest} />\n' +
109+
' }\n' +
110+
' }}\n' +
111+
' >\n' +
112+
' {markdown}\n' +
113+
' </Markdown>,\n' +
114+
" document.querySelector('#content')\n" +
115+
')\n' +
116+
'```\n' +
117+
'\n' +
118+
'## More info?\n' +
119+
'\n' +
120+
'Much more info is available in the\n' +
121+
'[readme on GitHub](https://github.com/remarkjs/react-markdown)!\n' +
122+
'\n' +
123+
'***\n' +
124+
'\n' +
125+
'A component by [Espen Hovlandsdal](https://espen.codes/)';
126+
127+
// For logging.
128+
// page.on('console', msg => console.log('BROWSER LOG:', msg.text()));
129+
130+
await page.goto('http://localhost:5273/', { waitUntil: 'networkidle' });
131+
132+
// Ensure the root is loaded
133+
const appRoot = page.locator('[data-testid="snapshot-unit-test-app-root"]');
134+
await expect(appRoot).toBeVisible();
135+
136+
// Debug: Ensure CustomMessage is not already there
137+
const preRenderCheck = await page.isVisible('[data-testid="wowwow"]');
138+
expect(preRenderCheck).toBeFalsy(); // Assert false
139+
140+
// Simulate setting a message through a mocked action
141+
await page.evaluate((msg) => {
142+
if (typeof (window as any).setMessage === 'function') {
143+
(window as any).setMessage(msg);
144+
} else {
145+
throw new Error('setMessage not exposed!');
146+
}
147+
}, message);
148+
149+
await page.waitForSelector('[data-testid="wowwow"]', {
150+
state: 'visible',
151+
timeout: 5000, // Give React time to re-render
152+
});
153+
154+
const customMessage = page.locator('[data-testid="wowwow"]');
155+
await customMessage.isVisible();
156+
await expect(customMessage).toHaveScreenshot({ omitBackground: false });
157+
});

apps/visual-test/.gitignore

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
node_modules
11+
dist
12+
dist-ssr
13+
*.local
14+
15+
# Editor directories and files
16+
.vscode/*
17+
!.vscode/extensions.json
18+
.idea
19+
.DS_Store
20+
*.suo
21+
*.ntvs*
22+
*.njsproj
23+
*.sln
24+
*.sw?

apps/visual-test/index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Widget snapshot unit test</title>
8+
</head>
9+
<body>
10+
<div id="root"></div>
11+
<script type="module" src="/src/main.tsx"></script>
12+
</body>
13+
</html>

apps/visual-test/package.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "@sendbird/visual-unit-test",
3+
"private": true,
4+
"version": "0.0.0",
5+
"type": "module",
6+
"scripts": {
7+
"dev": "vite",
8+
"build": "tsc -b && vite build",
9+
"lint": "eslint .",
10+
"preview": "vite preview"
11+
},
12+
"dependencies": {
13+
"react": "^18.3.1",
14+
"react-dom": "^18.3.1"
15+
},
16+
"devDependencies": {
17+
"@eslint/js": "^9.15.0",
18+
"@types/react": "^18.3.12",
19+
"@types/react-dom": "^18.3.1",
20+
"@vitejs/plugin-react": "^4.3.4",
21+
"globals": "^15.12.0",
22+
"typescript": "~5.6.2",
23+
"typescript-eslint": "^8.15.0",
24+
"vite": "^6.0.1"
25+
}
26+
}

apps/visual-test/src/App.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { UserMessage } from '@sendbird/chat/message';
2+
import { useEffect, useState } from 'react';
3+
4+
import { getMockedUserMessage } from './utils.ts';
5+
import CustomMessage from '../../../src/components/CustomMessage.tsx';
6+
7+
const App = () => {
8+
const [message, setMessage] = useState<UserMessage | null>(getMockedUserMessage());
9+
10+
// Expose setMessage for testing purposes
11+
useEffect(() => {
12+
if (import.meta.env.MODE === 'test') {
13+
(window as any).setMessage = (msg: string) => {
14+
setMessage(getMockedUserMessage(msg));
15+
};
16+
}
17+
}, []);
18+
19+
return (
20+
<div data-testid="snapshot-unit-test-app-root">
21+
<h1>Test</h1>
22+
{message && (
23+
<div data-testid="wowwow">
24+
{/*FIXME: Note that below component must be wrapped with ChatProvider.*/}
25+
<CustomMessage message={message} activeSpinnerId={-1} />
26+
</div>
27+
)}
28+
</div>
29+
);
30+
};
31+
32+
export default App;

apps/visual-test/src/const.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
export const mockUserMessage = {
2+
'_iid': 'su-64bae57b-1bea-48c9-92cf-11bbd4894339',
3+
'channelType': 'group',
4+
'messageType': 'user',
5+
'mentionType': 'users',
6+
'mentionedUsers': [],
7+
'mentionedUserIds': [],
8+
'metaArrays': [],
9+
'extendedMessage': {},
10+
'createdAt': 1728278180937,
11+
'updatedAt': 0,
12+
'channelUrl': 'sendbird_group_channel_200173252_e51dc54565dfb9f22e4c000f7b5edb89c7daf64f',
13+
'data':
14+
'{"suggested_replies": ["Go to Custom response 2"], "respond_mesg_id": 7619141906, "workflow": {"name": "mode test workflow", "intent_type": "exact_question_match"}}',
15+
'customType': '',
16+
'parentMessage': null,
17+
'silent': false,
18+
'isOperatorMessage': false,
19+
'threadInfo': null,
20+
'reactions': [],
21+
'appleCriticalAlertOptions': null,
22+
'scheduledInfo': null,
23+
'suggestedReplies': null,
24+
'myFeedback': null,
25+
'myFeedbackStatus': 'NOT_APPLICABLE',
26+
'_isContinuousMessages': false,
27+
'_scheduledStatus': null,
28+
'messageId': 7619141941,
29+
'parentMessageId': 0,
30+
'ogMetaData': null,
31+
'reqId': '',
32+
'replyToChannel': false,
33+
'errorCode': 0,
34+
'sender': {
35+
'_iid': 'su-64bae57b-1bea-48c9-92cf-11bbd4894339',
36+
'userId': 'onboarding_bot',
37+
'nickname': 'My first bot',
38+
'plainProfileUrl': 'https://file-ap-2.sendbird.com/f80c1e5cb1d74af5ad3e1ecc0679648d.gif',
39+
'requireAuth': false,
40+
'metaData': {},
41+
'connectionStatus': 'nonavailable',
42+
'isActive': true,
43+
'lastSeenAt': null,
44+
'preferredLanguages': null,
45+
'friendDiscoveryKey': null,
46+
'friendName': null,
47+
'isBlockedByMe': false,
48+
'role': 'none',
49+
},
50+
'sendingStatus': 'succeeded',
51+
'message': '',
52+
'messageParams': null,
53+
'translations': {},
54+
'translationTargetLanguages': [],
55+
'messageSurvivalSeconds': -1,
56+
'plugins': [],
57+
'_poll': null,
58+
};

apps/visual-test/src/main.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { StrictMode } from 'react';
2+
import { createRoot } from 'react-dom/client';
3+
4+
import App from './App.tsx';
5+
6+
createRoot(document.getElementById('root') as HTMLElement).render(
7+
<StrictMode>
8+
<App />
9+
</StrictMode>,
10+
);

apps/visual-test/src/utils.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import SendbirdChat from '@sendbird/chat';
2+
import { UserMessage } from '@sendbird/chat/message';
3+
4+
import { mockUserMessage } from './const';
5+
6+
const chat = SendbirdChat.init({
7+
appId: '',
8+
modules: [],
9+
});
10+
11+
export function getMockedUserMessage(msg = ''): UserMessage {
12+
return chat.message.buildMessageFromSerializedData({
13+
...mockUserMessage,
14+
message: msg,
15+
}) as UserMessage;
16+
}

apps/visual-test/src/vite-env.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/// <reference types="vite/client" />

apps/visual-test/tsconfig.app.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"compilerOptions": {
3+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4+
"target": "ES2020",
5+
"useDefineForClassFields": true,
6+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
7+
"module": "ESNext",
8+
"skipLibCheck": true,
9+
10+
/* Bundler mode */
11+
"moduleResolution": "bundler",
12+
"allowImportingTsExtensions": true,
13+
"isolatedModules": true,
14+
"moduleDetection": "force",
15+
"noEmit": true,
16+
"jsx": "react-jsx",
17+
18+
/* Linting */
19+
"strict": true,
20+
"noUnusedLocals": true,
21+
"noUnusedParameters": true,
22+
"noFallthroughCasesInSwitch": true,
23+
"noUncheckedSideEffectImports": true
24+
},
25+
"include": ["src"]
26+
}

apps/visual-test/tsconfig.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"files": [],
3+
"references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }],
4+
"compilerOptions": {
5+
"typeRoots": ["../../node_modules/@types", "./"], // Look in the current directory
6+
"types": []
7+
},
8+
"include": ["vite.config.ts", "src"]
9+
}

0 commit comments

Comments
 (0)