Files
gochat/frontend/app/javascript/shared/helpers/specs/clipboard.spec.js
T
rogee 321c61aaae Vendor Chatwoot Vue 3 frontend into frontend/
Copy the Chatwoot (v4.14.0) frontend runnable subset into frontend/ for
customization:
- app/javascript/ (Vue SPA: dashboard, widget, sdk, portal, superadmin)
- app/views/ (ERB templates for vite-plugin-ruby entrypoint resolution)
- app/helpers/, app/assets/ (Rails view helpers, static assets)
- enterprise/ (Enterprise edition frontend overlay)
- config/vite.json, vite.config.ts, bin/vite (Vite-Rails toolchain)
- package.json, pnpm-lock.yaml, tailwind/postcss/eslint configs
- Gemfile, Gemfile.lock (vite_rails gem for bin/vite binstub)

Excluded Rails backend: controllers, models, services, jobs, mailers,
policies, db, lib, spec, public, node_modules.

Update references to the new frontend location:
- .gitignore: exclude frontend build artifacts (node_modules, tmp, packs),
  keep frontend/bin/ and frontend/vendor/ via negation
- backend/scripts/parity_frontend_smoke.sh: CHATWOOT_DIR default
  reference/chatwoot -> ../frontend
- backend/scripts/parity_frontend_browser_smoke.mjs: same default update
- AGENTS.md: add frontend section with Rails/Vite coupling notes
- README: architecture tree includes frontend/
2026-07-07 14:56:01 +08:00

285 lines
8.3 KiB
JavaScript

import { copyTextToClipboard, handleOtpPaste } from '../clipboard';
const mockWriteText = vi.fn();
Object.assign(navigator, {
clipboard: {
writeText: mockWriteText,
},
});
describe('copyTextToClipboard', () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe('with string input', () => {
it('copies plain text string to clipboard', async () => {
const text = 'Hello World';
await copyTextToClipboard(text);
expect(mockWriteText).toHaveBeenCalledWith('Hello World');
expect(mockWriteText).toHaveBeenCalledTimes(1);
});
it('copies empty string to clipboard', async () => {
const text = '';
await copyTextToClipboard(text);
expect(mockWriteText).toHaveBeenCalledWith('');
expect(mockWriteText).toHaveBeenCalledTimes(1);
});
});
describe('with number input', () => {
it('converts number to string', async () => {
await copyTextToClipboard(42);
expect(mockWriteText).toHaveBeenCalledWith('42');
expect(mockWriteText).toHaveBeenCalledTimes(1);
});
it('converts zero to string', async () => {
await copyTextToClipboard(0);
expect(mockWriteText).toHaveBeenCalledWith('0');
expect(mockWriteText).toHaveBeenCalledTimes(1);
});
});
describe('with boolean input', () => {
it('converts true to string', async () => {
await copyTextToClipboard(true);
expect(mockWriteText).toHaveBeenCalledWith('true');
expect(mockWriteText).toHaveBeenCalledTimes(1);
});
it('converts false to string', async () => {
await copyTextToClipboard(false);
expect(mockWriteText).toHaveBeenCalledWith('false');
expect(mockWriteText).toHaveBeenCalledTimes(1);
});
});
describe('with null/undefined input', () => {
it('converts null to empty string', async () => {
await copyTextToClipboard(null);
expect(mockWriteText).toHaveBeenCalledWith('');
expect(mockWriteText).toHaveBeenCalledTimes(1);
});
it('converts undefined to empty string', async () => {
await copyTextToClipboard(undefined);
expect(mockWriteText).toHaveBeenCalledWith('');
expect(mockWriteText).toHaveBeenCalledTimes(1);
});
});
describe('with object input', () => {
it('stringifies simple object with proper formatting', async () => {
const obj = { name: 'John', age: 30 };
await copyTextToClipboard(obj);
const expectedJson = JSON.stringify(obj, null, 2);
expect(mockWriteText).toHaveBeenCalledWith(expectedJson);
expect(mockWriteText).toHaveBeenCalledTimes(1);
});
it('stringifies nested object with proper formatting', async () => {
const nestedObj = {
severity: {
user_id: 1181505,
user_name: 'test',
server_name: '[1253]test1253',
},
};
await copyTextToClipboard(nestedObj);
const expectedJson = JSON.stringify(nestedObj, null, 2);
expect(mockWriteText).toHaveBeenCalledWith(expectedJson);
expect(mockWriteText).toHaveBeenCalledTimes(1);
});
it('stringifies array with proper formatting', async () => {
const arr = [1, 2, { name: 'test' }];
await copyTextToClipboard(arr);
const expectedJson = JSON.stringify(arr, null, 2);
expect(mockWriteText).toHaveBeenCalledWith(expectedJson);
expect(mockWriteText).toHaveBeenCalledTimes(1);
});
it('stringifies empty object', async () => {
const obj = {};
await copyTextToClipboard(obj);
expect(mockWriteText).toHaveBeenCalledWith('{}');
expect(mockWriteText).toHaveBeenCalledTimes(1);
});
it('stringifies empty array', async () => {
const arr = [];
await copyTextToClipboard(arr);
expect(mockWriteText).toHaveBeenCalledWith('[]');
expect(mockWriteText).toHaveBeenCalledTimes(1);
});
});
describe('error handling', () => {
it('throws error when clipboard API fails', async () => {
const error = new Error('Clipboard access denied');
mockWriteText.mockRejectedValueOnce(error);
await expect(copyTextToClipboard('test')).rejects.toThrow(
'Unable to copy text to clipboard: Clipboard access denied'
);
});
it('handles clipboard API not available', async () => {
// Temporarily remove clipboard API
const originalClipboard = navigator.clipboard;
delete navigator.clipboard;
await expect(copyTextToClipboard('test')).rejects.toThrow(
'Unable to copy text to clipboard:'
);
// Restore clipboard API
navigator.clipboard = originalClipboard;
});
});
describe('edge cases', () => {
it('handles Date objects', async () => {
const date = new Date('2023-01-01T00:00:00.000Z');
await copyTextToClipboard(date);
const expectedJson = JSON.stringify(date, null, 2);
expect(mockWriteText).toHaveBeenCalledWith(expectedJson);
expect(mockWriteText).toHaveBeenCalledTimes(1);
});
it('handles functions by converting to string', async () => {
const func = () => 'test';
await copyTextToClipboard(func);
expect(mockWriteText).toHaveBeenCalledWith(func.toString());
expect(mockWriteText).toHaveBeenCalledTimes(1);
});
});
});
describe('handleOtpPaste', () => {
// Helper function to create mock clipboard event
const createMockPasteEvent = text => ({
clipboardData: {
getData: vi.fn().mockReturnValue(text),
},
});
describe('valid OTP paste scenarios', () => {
it('extracts 6-digit OTP from clean numeric string', () => {
const event = createMockPasteEvent('123456');
const result = handleOtpPaste(event);
expect(result).toBe('123456');
expect(event.clipboardData.getData).toHaveBeenCalledWith('text');
});
it('extracts 6-digit OTP from string with spaces', () => {
const event = createMockPasteEvent('1 2 3 4 5 6');
const result = handleOtpPaste(event);
expect(result).toBe('123456');
});
it('extracts 6-digit OTP from string with dashes', () => {
const event = createMockPasteEvent('123-456');
const result = handleOtpPaste(event);
expect(result).toBe('123456');
});
it('handles negative numbers by extracting digits only', () => {
const event = createMockPasteEvent('-123456');
const result = handleOtpPaste(event);
expect(result).toBe('123456');
});
it('handles decimal numbers by extracting digits only', () => {
const event = createMockPasteEvent('123.456');
const result = handleOtpPaste(event);
expect(result).toBe('123456');
});
it('extracts 6-digit OTP from mixed alphanumeric string', () => {
const event = createMockPasteEvent('Your code is: 987654');
const result = handleOtpPaste(event);
expect(result).toBe('987654');
});
it('extracts first 6 digits when more than 6 digits present', () => {
const event = createMockPasteEvent('12345678901234');
const result = handleOtpPaste(event);
expect(result).toBe('123456');
});
it('handles custom maxLength parameter', () => {
const event = createMockPasteEvent('12345678');
const result = handleOtpPaste(event, 8);
expect(result).toBe('12345678');
});
it('extracts 4-digit OTP with custom maxLength', () => {
const event = createMockPasteEvent('Your PIN: 9876');
const result = handleOtpPaste(event, 4);
expect(result).toBe('9876');
});
});
describe('invalid OTP paste scenarios', () => {
it('returns null for insufficient digits', () => {
const event = createMockPasteEvent('12345');
const result = handleOtpPaste(event);
expect(result).toBeNull();
});
it('returns null for text with no digits', () => {
const event = createMockPasteEvent('Hello World');
const result = handleOtpPaste(event);
expect(result).toBeNull();
});
it('returns null for empty string', () => {
const event = createMockPasteEvent('');
const result = handleOtpPaste(event);
expect(result).toBeNull();
});
it('returns null when event is null', () => {
const result = handleOtpPaste(null);
expect(result).toBeNull();
});
it('returns null when event is undefined', () => {
const result = handleOtpPaste(undefined);
expect(result).toBeNull();
});
});
});