Merge branch 'develop' into fix/email-original-timestamp

This commit is contained in:
Vinay Keerthi
2025-12-17 19:45:35 +05:30
committed by GitHub
6 changed files with 289 additions and 13 deletions
+1 -1
View File
@@ -76,7 +76,7 @@ const runSDK = ({ baseUrl, websiteToken }) => {
welcomeDescription: chatwootSettings.welcomeDescription || '',
availableMessage: chatwootSettings.availableMessage || '',
unavailableMessage: chatwootSettings.unavailableMessage || '',
enableFileUpload: chatwootSettings.enableFileUpload ?? true,
enableFileUpload: chatwootSettings.enableFileUpload,
enableEmojiPicker: chatwootSettings.enableEmojiPicker ?? true,
enableEndConversation: chatwootSettings.enableEndConversation ?? true,
@@ -11,6 +11,7 @@ import FluentIcon from 'shared/components/FluentIcon/Index.vue';
import { DirectUpload } from 'activestorage';
import { mapGetters } from 'vuex';
import { emitter } from 'shared/helpers/mitt';
import { useAttachments } from '../composables/useAttachments';
export default {
components: { FluentIcon, FileUpload, Spinner },
@@ -20,13 +21,16 @@ export default {
default: () => {},
},
},
setup() {
const { canHandleAttachments } = useAttachments();
return { canHandleAttachments };
},
data() {
return { isUploading: false };
},
computed: {
...mapGetters({
globalConfig: 'globalConfig/get',
shouldShowFilePicker: 'appConfig/getShouldShowFilePicker',
}),
fileUploadSizeLimit() {
return resolveMaximumFileUploadSize(
@@ -46,7 +50,7 @@ export default {
methods: {
handleClipboardPaste(e) {
// If file picker is not enabled, do not allow paste
if (!this.shouldShowFilePicker) return;
if (!this.canHandleAttachments) return;
const items = (e.clipboardData || e.originalEvent.clipboardData).items;
// items is a DataTransferItemList object which does not have forEach method
@@ -3,7 +3,7 @@ import { mapGetters } from 'vuex';
import ChatAttachmentButton from 'widget/components/ChatAttachment.vue';
import ChatSendButton from 'widget/components/ChatSendButton.vue';
import configMixin from '../mixins/configMixin';
import { useAttachments } from '../composables/useAttachments';
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
import ResizableTextArea from 'shared/components/ResizableTextArea.vue';
@@ -18,7 +18,6 @@ export default {
FluentIcon,
ResizableTextArea,
},
mixins: [configMixin],
props: {
onSendMessage: {
type: Function,
@@ -29,6 +28,18 @@ export default {
default: () => {},
},
},
setup() {
const {
canHandleAttachments,
shouldShowEmojiPicker,
hasEmojiPickerEnabled,
} = useAttachments();
return {
canHandleAttachments,
shouldShowEmojiPicker,
hasEmojiPickerEnabled,
};
},
data() {
return {
userInput: '',
@@ -41,15 +52,10 @@ export default {
...mapGetters({
widgetColor: 'appConfig/getWidgetColor',
isWidgetOpen: 'appConfig/getIsWidgetOpen',
shouldShowFilePicker: 'appConfig/getShouldShowFilePicker',
shouldShowEmojiPicker: 'appConfig/getShouldShowEmojiPicker',
}),
showAttachment() {
return (
this.shouldShowFilePicker &&
this.hasAttachmentsEnabled &&
this.userInput.length === 0
);
return this.canHandleAttachments && this.userInput.length === 0;
},
showSendButton() {
return this.userInput.length > 0;
@@ -0,0 +1,224 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { useAttachments } from '../useAttachments';
import { useStore } from 'vuex';
import { computed } from 'vue';
// Mock Vue's useStore
vi.mock('vuex', () => ({
useStore: vi.fn(),
}));
// Mock Vue's computed
vi.mock('vue', () => ({
computed: vi.fn(fn => ({ value: fn() })),
}));
describe('useAttachments', () => {
let mockStore;
let mockGetters;
beforeEach(() => {
// Reset window.chatwootWebChannel
delete window.chatwootWebChannel;
// Create mock store
mockGetters = {};
mockStore = {
getters: mockGetters,
};
vi.mocked(useStore).mockReturnValue(mockStore);
// Mock computed to return a reactive-like object
vi.mocked(computed).mockImplementation(fn => ({ value: fn() }));
});
afterEach(() => {
vi.clearAllMocks();
});
describe('shouldShowFilePicker', () => {
it('returns value from store getter', () => {
mockGetters['appConfig/getShouldShowFilePicker'] = true;
const { shouldShowFilePicker } = useAttachments();
expect(shouldShowFilePicker.value).toBe(true);
});
it('returns undefined when not set in store', () => {
mockGetters['appConfig/getShouldShowFilePicker'] = undefined;
const { shouldShowFilePicker } = useAttachments();
expect(shouldShowFilePicker.value).toBeUndefined();
});
});
describe('hasAttachmentsEnabled', () => {
it('returns true when attachments are enabled in channel config', () => {
window.chatwootWebChannel = {
enabledFeatures: ['attachments', 'emoji'],
};
const { hasAttachmentsEnabled } = useAttachments();
expect(hasAttachmentsEnabled.value).toBe(true);
});
it('returns false when attachments are not enabled in channel config', () => {
window.chatwootWebChannel = {
enabledFeatures: ['emoji'],
};
const { hasAttachmentsEnabled } = useAttachments();
expect(hasAttachmentsEnabled.value).toBe(false);
});
it('returns false when channel config has no enabled features', () => {
window.chatwootWebChannel = {
enabledFeatures: [],
};
const { hasAttachmentsEnabled } = useAttachments();
expect(hasAttachmentsEnabled.value).toBe(false);
});
it('returns false when channel config is missing', () => {
window.chatwootWebChannel = undefined;
const { hasAttachmentsEnabled } = useAttachments();
expect(hasAttachmentsEnabled.value).toBe(false);
});
it('returns false when enabledFeatures is missing', () => {
window.chatwootWebChannel = {};
const { hasAttachmentsEnabled } = useAttachments();
expect(hasAttachmentsEnabled.value).toBe(false);
});
});
describe('canHandleAttachments', () => {
beforeEach(() => {
// Set up a default channel config
window.chatwootWebChannel = {
enabledFeatures: ['attachments'],
};
});
it('prioritizes SDK flag when explicitly set to true', () => {
mockGetters['appConfig/getShouldShowFilePicker'] = true;
const { canHandleAttachments } = useAttachments();
expect(canHandleAttachments.value).toBe(true);
});
it('prioritizes SDK flag when explicitly set to false', () => {
mockGetters['appConfig/getShouldShowFilePicker'] = false;
const { canHandleAttachments } = useAttachments();
expect(canHandleAttachments.value).toBe(false);
});
it('falls back to inbox settings when SDK flag is undefined', () => {
mockGetters['appConfig/getShouldShowFilePicker'] = undefined;
window.chatwootWebChannel = {
enabledFeatures: ['attachments'],
};
const { canHandleAttachments } = useAttachments();
expect(canHandleAttachments.value).toBe(true);
});
it('falls back to inbox settings when SDK flag is undefined and attachments disabled', () => {
mockGetters['appConfig/getShouldShowFilePicker'] = undefined;
window.chatwootWebChannel = {
enabledFeatures: ['emoji'],
};
const { canHandleAttachments } = useAttachments();
expect(canHandleAttachments.value).toBe(false);
});
it('prioritizes SDK false over inbox settings true', () => {
mockGetters['appConfig/getShouldShowFilePicker'] = false;
window.chatwootWebChannel = {
enabledFeatures: ['attachments'],
};
const { canHandleAttachments } = useAttachments();
expect(canHandleAttachments.value).toBe(false);
});
it('prioritizes SDK true over inbox settings false', () => {
mockGetters['appConfig/getShouldShowFilePicker'] = true;
window.chatwootWebChannel = {
enabledFeatures: ['emoji'], // no attachments
};
const { canHandleAttachments } = useAttachments();
expect(canHandleAttachments.value).toBe(true);
});
});
describe('hasEmojiPickerEnabled', () => {
it('returns true when emoji picker is enabled in channel config', () => {
window.chatwootWebChannel = {
enabledFeatures: ['emoji_picker', 'attachments'],
};
const { hasEmojiPickerEnabled } = useAttachments();
expect(hasEmojiPickerEnabled.value).toBe(true);
});
it('returns false when emoji picker is not enabled in channel config', () => {
window.chatwootWebChannel = {
enabledFeatures: ['attachments'],
};
const { hasEmojiPickerEnabled } = useAttachments();
expect(hasEmojiPickerEnabled.value).toBe(false);
});
});
describe('shouldShowEmojiPicker', () => {
it('returns value from store getter', () => {
mockGetters['appConfig/getShouldShowEmojiPicker'] = true;
const { shouldShowEmojiPicker } = useAttachments();
expect(shouldShowEmojiPicker.value).toBe(true);
});
});
describe('integration test', () => {
it('returns all expected properties', () => {
mockGetters['appConfig/getShouldShowFilePicker'] = undefined;
mockGetters['appConfig/getShouldShowEmojiPicker'] = true;
window.chatwootWebChannel = {
enabledFeatures: ['attachments', 'emoji_picker'],
};
const result = useAttachments();
expect(result).toHaveProperty('shouldShowFilePicker');
expect(result).toHaveProperty('shouldShowEmojiPicker');
expect(result).toHaveProperty('hasAttachmentsEnabled');
expect(result).toHaveProperty('hasEmojiPickerEnabled');
expect(result).toHaveProperty('canHandleAttachments');
expect(Object.keys(result)).toHaveLength(5);
});
});
});
@@ -0,0 +1,42 @@
import { computed } from 'vue';
import { useStore } from 'vuex';
export function useAttachments() {
const store = useStore();
const shouldShowFilePicker = computed(
() => store.getters['appConfig/getShouldShowFilePicker']
);
const shouldShowEmojiPicker = computed(
() => store.getters['appConfig/getShouldShowEmojiPicker']
);
const hasAttachmentsEnabled = computed(() => {
const channelConfig = window.chatwootWebChannel;
return channelConfig?.enabledFeatures?.includes('attachments') || false;
});
const hasEmojiPickerEnabled = computed(() => {
const channelConfig = window.chatwootWebChannel;
return channelConfig?.enabledFeatures?.includes('emoji_picker') || false;
});
const canHandleAttachments = computed(() => {
// If enableFileUpload was explicitly set via SDK, prioritize that
if (shouldShowFilePicker.value !== undefined) {
return shouldShowFilePicker.value;
}
// Otherwise, fall back to inbox settings only
return hasAttachmentsEnabled.value;
});
return {
shouldShowFilePicker,
shouldShowEmojiPicker,
hasAttachmentsEnabled,
hasEmojiPickerEnabled,
canHandleAttachments,
};
}
@@ -25,7 +25,7 @@ const state = {
welcomeDescription: '',
availableMessage: '',
unavailableMessage: '',
enableFileUpload: true,
enableFileUpload: undefined,
enableEmojiPicker: true,
enableEndConversation: true,
};
@@ -64,7 +64,7 @@ export const actions = {
welcomeDescription = '',
availableMessage = '',
unavailableMessage = '',
enableFileUpload = true,
enableFileUpload = undefined,
enableEmojiPicker = true,
enableEndConversation = true,
}