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 #399

Merged
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
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
Loading