Skip to content

Commit

Permalink
Added unit test for markdown message
Browse files Browse the repository at this point in the history
  • Loading branch information
liamcho committed Dec 11, 2024
1 parent ad9576f commit 42dbdcd
Show file tree
Hide file tree
Showing 18 changed files with 3,515 additions and 5,159 deletions.
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} />
</div>
)}
</div>
);
};

export default App;
58 changes: 58 additions & 0 deletions apps/visual-test/src/const.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
export const mockUserMessage = {
'_iid': 'su-64bae57b-1bea-48c9-92cf-11bbd4894339',
'channelType': 'group',
'messageType': 'user',
'mentionType': 'users',
'mentionedUsers': [],
'mentionedUserIds': [],
'metaArrays': [],
'extendedMessage': {},
'createdAt': 1728278180937,
'updatedAt': 0,
'channelUrl': 'sendbird_group_channel_200173252_e51dc54565dfb9f22e4c000f7b5edb89c7daf64f',
'data':
'{"suggested_replies": ["Go to Custom response 2"], "respond_mesg_id": 7619141906, "workflow": {"name": "mode test workflow", "intent_type": "exact_question_match"}}',
'customType': '',
'parentMessage': null,
'silent': false,
'isOperatorMessage': false,
'threadInfo': null,
'reactions': [],
'appleCriticalAlertOptions': null,
'scheduledInfo': null,
'suggestedReplies': null,
'myFeedback': null,
'myFeedbackStatus': 'NOT_APPLICABLE',
'_isContinuousMessages': false,
'_scheduledStatus': null,
'messageId': 7619141941,
'parentMessageId': 0,
'ogMetaData': null,
'reqId': '',
'replyToChannel': false,
'errorCode': 0,
'sender': {
'_iid': 'su-64bae57b-1bea-48c9-92cf-11bbd4894339',
'userId': 'onboarding_bot',
'nickname': 'My first bot',
'plainProfileUrl': 'https://file-ap-2.sendbird.com/f80c1e5cb1d74af5ad3e1ecc0679648d.gif',
'requireAuth': false,
'metaData': {},
'connectionStatus': 'nonavailable',
'isActive': true,
'lastSeenAt': null,
'preferredLanguages': null,
'friendDiscoveryKey': null,
'friendName': null,
'isBlockedByMe': false,
'role': 'none',
},
'sendingStatus': 'succeeded',
'message': '',
'messageParams': null,
'translations': {},
'translationTargetLanguages': [],
'messageSurvivalSeconds': -1,
'plugins': [],
'_poll': null,
};
10 changes: 10 additions & 0 deletions apps/visual-test/src/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';

import App from './App.tsx';

createRoot(document.getElementById('root') as HTMLElement).render(
<StrictMode>
<App />
</StrictMode>,
);
16 changes: 16 additions & 0 deletions apps/visual-test/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import SendbirdChat from '@sendbird/chat';
import { UserMessage } from '@sendbird/chat/message';

import { mockUserMessage } from './const';

const chat = SendbirdChat.init({
appId: '',
modules: [],
});

export function getMockedUserMessage(msg = ''): UserMessage {
return chat.message.buildMessageFromSerializedData({
...mockUserMessage,
message: msg,
}) as UserMessage;
}
1 change: 1 addition & 0 deletions apps/visual-test/src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="vite/client" />
26 changes: 26 additions & 0 deletions apps/visual-test/tsconfig.app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,

/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",

/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
}
9 changes: 9 additions & 0 deletions apps/visual-test/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"files": [],
"references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }],
"compilerOptions": {
"typeRoots": ["../../node_modules/@types", "./"], // Look in the current directory
"types": []
},
"include": ["vite.config.ts", "src"]
}
Loading

0 comments on commit 42dbdcd

Please sign in to comment.