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/
140 lines
5.0 KiB
JavaScript
140 lines
5.0 KiB
JavaScript
import {
|
|
isPdfDocument,
|
|
isSafeHttpLink,
|
|
formatDocumentLink,
|
|
} from 'shared/helpers/documentHelper';
|
|
|
|
describe('documentHelper', () => {
|
|
describe('#isPdfDocument', () => {
|
|
it('returns true for PDF documents', () => {
|
|
expect(isPdfDocument('PDF:document.pdf')).toBe(true);
|
|
expect(isPdfDocument('PDF:my-file_20241227123045.pdf')).toBe(true);
|
|
expect(isPdfDocument('PDF:report with spaces_20241227123045.pdf')).toBe(
|
|
true
|
|
);
|
|
});
|
|
|
|
it('returns false for regular URLs', () => {
|
|
expect(isPdfDocument('https://example.com')).toBe(false);
|
|
expect(isPdfDocument('http://docs.example.com/file.pdf')).toBe(false);
|
|
expect(isPdfDocument('ftp://files.example.com/document.pdf')).toBe(false);
|
|
});
|
|
|
|
it('returns false for empty or null values', () => {
|
|
expect(isPdfDocument('')).toBe(false);
|
|
expect(isPdfDocument(null)).toBe(false);
|
|
expect(isPdfDocument(undefined)).toBe(false);
|
|
});
|
|
|
|
it('returns false for strings that contain PDF but do not start with PDF:', () => {
|
|
expect(isPdfDocument('document PDF:file.pdf')).toBe(false);
|
|
expect(isPdfDocument('My PDF:file.pdf')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('#isSafeHttpLink', () => {
|
|
it('returns true for http and https URLs', () => {
|
|
expect(isSafeHttpLink('http://example.com')).toBe(true);
|
|
expect(isSafeHttpLink('https://example.com/path?q=1#x')).toBe(true);
|
|
expect(isSafeHttpLink('HTTPS://EXAMPLE.COM')).toBe(true);
|
|
});
|
|
|
|
/* eslint-disable no-script-url */
|
|
it('returns false for javascript: and other dangerous schemes', () => {
|
|
expect(isSafeHttpLink('javascript:alert(1)')).toBe(false);
|
|
expect(isSafeHttpLink('JavaScript:alert(1)')).toBe(false);
|
|
expect(isSafeHttpLink('data:text/html,<script>alert(1)</script>')).toBe(
|
|
false
|
|
);
|
|
expect(isSafeHttpLink('vbscript:msgbox(1)')).toBe(false);
|
|
expect(isSafeHttpLink('file:///etc/passwd')).toBe(false);
|
|
expect(isSafeHttpLink('ftp://files.example.com/doc.pdf')).toBe(false);
|
|
});
|
|
/* eslint-enable no-script-url */
|
|
|
|
it('returns false for invalid or empty values', () => {
|
|
expect(isSafeHttpLink('')).toBe(false);
|
|
expect(isSafeHttpLink(null)).toBe(false);
|
|
expect(isSafeHttpLink(undefined)).toBe(false);
|
|
expect(isSafeHttpLink('not a url')).toBe(false);
|
|
expect(isSafeHttpLink('//example.com')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('#formatDocumentLink', () => {
|
|
describe('PDF documents', () => {
|
|
it('removes PDF: prefix from PDF documents', () => {
|
|
expect(formatDocumentLink('PDF:document.pdf')).toBe('document.pdf');
|
|
expect(formatDocumentLink('PDF:my-file.pdf')).toBe('my-file.pdf');
|
|
});
|
|
|
|
it('removes timestamp suffix from PDF documents', () => {
|
|
expect(formatDocumentLink('PDF:document_20241227123045.pdf')).toBe(
|
|
'document.pdf'
|
|
);
|
|
expect(formatDocumentLink('PDF:report_20231215094530.pdf')).toBe(
|
|
'report.pdf'
|
|
);
|
|
});
|
|
|
|
it('handles PDF documents with spaces in filename', () => {
|
|
expect(formatDocumentLink('PDF:my document_20241227123045.pdf')).toBe(
|
|
'my document.pdf'
|
|
);
|
|
expect(
|
|
formatDocumentLink('PDF:Annual Report 2024_20241227123045.pdf')
|
|
).toBe('Annual Report 2024.pdf');
|
|
});
|
|
|
|
it('handles PDF documents without timestamp suffix', () => {
|
|
expect(formatDocumentLink('PDF:document.pdf')).toBe('document.pdf');
|
|
expect(formatDocumentLink('PDF:simple-file.pdf')).toBe(
|
|
'simple-file.pdf'
|
|
);
|
|
});
|
|
|
|
it('handles PDF documents with partial timestamp patterns', () => {
|
|
expect(formatDocumentLink('PDF:document_202412.pdf')).toBe(
|
|
'document_202412.pdf'
|
|
);
|
|
expect(formatDocumentLink('PDF:file_123.pdf')).toBe('file_123.pdf');
|
|
});
|
|
|
|
it('handles edge cases with timestamp pattern', () => {
|
|
expect(
|
|
formatDocumentLink('PDF:doc_20241227123045_final_20241227123045.pdf')
|
|
).toBe('doc_20241227123045_final.pdf');
|
|
});
|
|
});
|
|
|
|
describe('Regular URLs', () => {
|
|
it('removes http(s) and www prefixes for compact display', () => {
|
|
expect(formatDocumentLink('https://example.com')).toBe('example.com');
|
|
expect(formatDocumentLink('http://docs.example.com/api')).toBe(
|
|
'docs.example.com/api'
|
|
);
|
|
expect(formatDocumentLink('https://www.github.com/user/repo')).toBe(
|
|
'github.com/user/repo'
|
|
);
|
|
});
|
|
|
|
it('handles URLs with query parameters', () => {
|
|
expect(formatDocumentLink('https://example.com?param=value')).toBe(
|
|
'example.com?param=value'
|
|
);
|
|
expect(
|
|
formatDocumentLink(
|
|
'https://api.example.com/docs?version=v1&format=json'
|
|
)
|
|
).toBe('api.example.com/docs?version=v1&format=json');
|
|
});
|
|
|
|
it('handles URLs with fragments', () => {
|
|
expect(formatDocumentLink('https://example.com/docs#section1')).toBe(
|
|
'example.com/docs#section1'
|
|
);
|
|
});
|
|
});
|
|
});
|
|
});
|