Initial commit: Add logistics and order_detail message types
Some checks failed
Lock Threads / action (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
Publish Chatwoot EE docker images / build (linux/amd64, ubuntu-latest) (push) Has been cancelled
Publish Chatwoot EE docker images / build (linux/arm64, ubuntu-22.04-arm) (push) Has been cancelled
Publish Chatwoot EE docker images / merge (push) Has been cancelled
Publish Chatwoot CE docker images / build (linux/amd64, ubuntu-latest) (push) Has been cancelled
Publish Chatwoot CE docker images / build (linux/arm64, ubuntu-22.04-arm) (push) Has been cancelled
Publish Chatwoot CE docker images / merge (push) Has been cancelled
Run Chatwoot CE spec / lint-backend (push) Has been cancelled
Run Chatwoot CE spec / lint-frontend (push) Has been cancelled
Run Chatwoot CE spec / frontend-tests (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (0, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (1, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (10, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (11, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (12, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (13, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (14, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (15, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (2, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (3, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (4, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (5, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (6, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (7, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (8, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (9, 16) (push) Has been cancelled
Run Linux nightly installer / nightly (push) Has been cancelled

- Add Logistics component with progress tracking
- Add OrderDetail component for order information
- Support data-driven steps and actions
- Add blue color scale to widget SCSS
- Fix node overflow and progress bar rendering issues
- Add English translations for dashboard components

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Liang XJ
2026-01-26 11:16:56 +08:00
commit 092fb2e083
7646 changed files with 975643 additions and 0 deletions

View File

@@ -0,0 +1,247 @@
import axios from 'axios';
import Cookies from 'js-cookie';
import { actions } from '../../auth';
import types from '../../../mutation-types';
import * as APIHelpers from '../../../utils/api';
import '../../../../routes';
vi.spyOn(APIHelpers, 'setUser');
vi.spyOn(APIHelpers, 'clearCookiesOnLogout');
vi.spyOn(APIHelpers, 'getHeaderExpiry');
vi.spyOn(Cookies, 'get');
const commit = vi.fn();
const dispatch = vi.fn();
global.axios = axios;
vi.mock('axios');
describe('#actions', () => {
describe('#validityCheck', () => {
it('sends correct actions if API is success', async () => {
axios.get.mockResolvedValue({
data: { payload: { data: { id: 1, name: 'John' } } },
headers: { expiry: 581842904 },
});
await actions.validityCheck({ commit });
expect(APIHelpers.setUser).toHaveBeenCalledTimes(1);
expect(commit.mock.calls).toEqual([
[types.SET_CURRENT_USER, { id: 1, name: 'John' }],
]);
});
it('sends correct actions if API is error', async () => {
axios.get.mockRejectedValue({
response: { status: 401 },
});
await actions.validityCheck({ commit });
expect(APIHelpers.clearCookiesOnLogout);
});
});
describe('#updateProfile', () => {
it('sends correct actions if API is success', async () => {
axios.put.mockResolvedValue({
data: { id: 1, name: 'John' },
headers: { expiry: 581842904 },
});
await actions.updateProfile({ commit }, { name: 'Pranav' });
expect(commit.mock.calls).toEqual([
[types.SET_CURRENT_USER, { id: 1, name: 'John' }],
]);
});
});
describe('#updateAvailability', () => {
it('sends correct actions if API is success', async () => {
axios.post.mockResolvedValue({
data: {
id: 1,
name: 'John',
accounts: [{ account_id: 1, availability_status: 'offline' }],
},
headers: { expiry: 581842904 },
});
await actions.updateAvailability(
{ commit, dispatch, getters: { getCurrentUserAvailability: 'online' } },
{ availability: 'offline', account_id: 1 }
);
expect(commit.mock.calls).toEqual([
[types.SET_CURRENT_USER_AVAILABILITY, 'offline'],
[
types.SET_CURRENT_USER,
{
id: 1,
name: 'John',
accounts: [{ account_id: 1, availability_status: 'offline' }],
},
],
]);
expect(dispatch.mock.calls).toEqual([
[
'agents/updateSingleAgentPresence',
{ availabilityStatus: 'offline', id: 1 },
],
]);
});
it('sends correct actions if API is a failure', async () => {
axios.post.mockRejectedValue({ error: 'Authentication Failure' });
await actions.updateAvailability(
{ commit, dispatch, getters: { getCurrentUserAvailability: 'online' } },
{ availability: 'offline', account_id: 1 }
);
expect(commit.mock.calls).toEqual([
[types.SET_CURRENT_USER_AVAILABILITY, 'offline'],
[types.SET_CURRENT_USER_AVAILABILITY, 'online'],
]);
});
});
describe('#updateAutoOffline', () => {
it('sends correct actions if API is success', async () => {
axios.post.mockResolvedValue({
data: {
id: 1,
name: 'John',
accounts: [
{
account_id: 1,
auto_offline: false,
},
],
},
headers: { expiry: 581842904 },
});
await actions.updateAutoOffline(
{ commit, dispatch, getters: { getCurrentUserAutoOffline: true } },
{ autoOffline: false, accountId: 1 }
);
expect(commit.mock.calls).toEqual([
[types.SET_CURRENT_USER_AUTO_OFFLINE, false],
[
types.SET_CURRENT_USER,
{
id: 1,
name: 'John',
accounts: [{ account_id: 1, auto_offline: false }],
},
],
]);
});
it('sends correct actions if API is failure', async () => {
axios.post.mockRejectedValue({ error: 'Authentication Failure' });
await actions.updateAutoOffline(
{ commit, dispatch, getters: { getCurrentUserAutoOffline: true } },
{ autoOffline: false, accountId: 1 }
);
expect(commit.mock.calls).toEqual([
[types.SET_CURRENT_USER_AUTO_OFFLINE, false],
[types.SET_CURRENT_USER_AUTO_OFFLINE, true],
]);
});
});
describe('#updateUISettings', () => {
it('sends correct actions if API is success', async () => {
axios.put.mockResolvedValue({
data: {
id: 1,
name: 'John',
availability_status: 'offline',
ui_settings: { is_contact_sidebar_open: true },
},
headers: { expiry: 581842904 },
});
await actions.updateUISettings(
{ commit, dispatch },
{ uiSettings: { is_contact_sidebar_open: false } }
);
expect(commit.mock.calls).toEqual([
[
types.SET_CURRENT_USER_UI_SETTINGS,
{ uiSettings: { is_contact_sidebar_open: false } },
],
[
types.SET_CURRENT_USER,
{
id: 1,
name: 'John',
availability_status: 'offline',
ui_settings: { is_contact_sidebar_open: true },
},
],
]);
});
});
describe('#setUser', () => {
it('sends correct actions if user is logged in', async () => {
Cookies.get.mockImplementation(() => true);
actions.setUser({ commit, dispatch });
expect(commit.mock.calls).toEqual([]);
expect(dispatch.mock.calls).toEqual([['validityCheck']]);
});
it('sends correct actions if user is not logged in', async () => {
Cookies.get.mockImplementation(() => false);
actions.setUser({ commit, dispatch });
expect(commit.mock.calls).toEqual([
[types.CLEAR_USER],
[types.SET_CURRENT_USER_UI_FLAGS, { isFetching: false }],
]);
expect(dispatch).toHaveBeenCalledTimes(0);
});
});
describe('#setCurrentUserAvailability', () => {
it('sends correct mutations if user id is available', async () => {
actions.setCurrentUserAvailability(
{
commit,
state: { currentUser: { id: 1 } },
},
{ 1: 'online' }
);
expect(commit.mock.calls).toEqual([
[types.SET_CURRENT_USER_AVAILABILITY, 'online'],
]);
});
it('does not send correct mutations if user id is not available', async () => {
actions.setCurrentUserAvailability(
{
commit,
state: { currentUser: { id: 1 } },
},
{}
);
expect(commit.mock.calls).toEqual([]);
});
});
describe('#setActiveAccount', () => {
it('sends correct mutations if account id is available', async () => {
actions.setActiveAccount(
{
commit,
},
{ accountId: 1 }
);
});
});
describe('#resetAccessToken', () => {
it('sends correct actions if API is success', async () => {
const mockResponse = {
data: { id: 1, name: 'John', access_token: 'new_token_123' },
headers: { expiry: 581842904 },
};
axios.post.mockResolvedValue(mockResponse);
const result = await actions.resetAccessToken({ commit });
expect(commit.mock.calls).toEqual([
[types.SET_CURRENT_USER, mockResponse.data],
]);
expect(result).toBe(true);
});
});
});

View File

@@ -0,0 +1,160 @@
import { getters } from '../../auth';
describe('#getters', () => {
describe('#isLoggedIn', () => {
it('return correct value if user data is available', () => {
expect(getters.isLoggedIn({ currentUser: { id: null } })).toEqual(false);
expect(getters.isLoggedIn({ currentUser: { id: 1 } })).toEqual(true);
});
});
describe('#getCurrentUser', () => {
it('returns current user id', () => {
expect(getters.getCurrentUserID({ currentUser: { id: 1 } })).toEqual(1);
});
});
describe('#getCurrentUser', () => {
it('returns current user object', () => {
expect(
getters.getCurrentUser({ currentUser: { id: 1, name: 'Pranav' } })
).toEqual({ id: 1, name: 'Pranav' });
});
});
describe('#getCurrentRole', () => {
it('returns current role if account is available', () => {
expect(
getters.getCurrentRole(
{ currentUser: { accounts: [{ id: 1, role: 'admin' }] } },
{ getCurrentAccountId: 1 }
)
).toEqual('admin');
});
it('returns undefined if account is not available', () => {
expect(
getters.getCurrentRole(
{ currentUser: { accounts: [{ id: 1, role: 'admin' }] } },
{ getCurrentAccountId: 2 }
)
).toEqual(undefined);
});
});
describe('#getCurrentCustomRoleId', () => {
it('returns current custom role id', () => {
expect(
getters.getCurrentCustomRoleId(
{ currentUser: { accounts: [{ id: 1, custom_role_id: 1 }] } },
{ getCurrentAccountId: 1 }
)
).toEqual(1);
});
it('returns undefined if account is not available', () => {
expect(
getters.getCurrentCustomRoleId(
{ currentUser: { accounts: [{ id: 1, custom_role_id: 1 }] } },
{ getCurrentAccountId: 2 }
)
).toEqual(undefined);
});
});
describe('#getCurrentUserAvailability', () => {
it('returns correct availability status', () => {
expect(
getters.getCurrentUserAvailability(
{
currentAccountId: 1,
currentUser: {
id: 1,
accounts: [{ id: 1, availability: 'busy' }],
},
},
{ getCurrentAccountId: 1 }
)
).toEqual('busy');
});
});
describe('#getUISettings', () => {
it('return correct UI Settings', () => {
expect(
getters.getUISettings({
currentUser: { ui_settings: { is_contact_sidebar_open: true } },
})
).toEqual({ is_contact_sidebar_open: true });
});
});
describe('#getMessageSignature', () => {
it('Return signature when signature is present', () => {
expect(
getters.getMessageSignature({
currentUser: { message_signature: 'Thanks' },
})
).toEqual('Thanks');
});
it('Return empty string when signature is not present', () => {
expect(getters.getMessageSignature({ currentUser: {} })).toEqual('');
});
});
describe('#getCurrentAccount', () => {
it('returns correct values', () => {
expect(
getters.getCurrentAccount({
currentUser: {},
})
).toEqual({});
expect(
getters.getCurrentAccount(
{
currentUser: {
accounts: [
{
name: 'Chatwoot',
id: 1,
},
],
},
currentAccountId: 1,
},
{ getCurrentAccountId: 1 }
)
).toEqual({
name: 'Chatwoot',
id: 1,
});
});
});
describe('#getUserAccounts', () => {
it('returns correct values', () => {
expect(
getters.getUserAccounts({
currentUser: {},
})
).toEqual([]);
expect(
getters.getUserAccounts({
currentUser: {
accounts: [
{
name: 'Chatwoot',
id: 1,
},
],
},
})
).toEqual([
{
name: 'Chatwoot',
id: 1,
},
]);
});
});
});

View File

@@ -0,0 +1,60 @@
import types from '../../../mutation-types';
import { mutations } from '../../auth';
describe('#mutations', () => {
describe('#SET_CURRENT_USER_UI_SETTINGS', () => {
it('set ui flags', () => {
const state = {
currentUser: {
ui_settings: { is_contact_sidebar_open: true, icon_type: 'emoji' },
},
};
mutations[types.SET_CURRENT_USER_UI_SETTINGS](state, {
uiSettings: { is_contact_sidebar_open: false },
});
expect(state.currentUser.ui_settings).toEqual({
is_contact_sidebar_open: false,
icon_type: 'emoji',
});
});
});
describe('#SET_CURRENT_USER_UI_FLAGS', () => {
it('set auth ui flags', () => {
const state = {
uiFlags: { isFetching: false },
};
mutations[types.SET_CURRENT_USER_UI_FLAGS](state, { isFetching: true });
expect(state.uiFlags.isFetching).toEqual(true);
});
});
describe('#CLEAR_USER', () => {
it('set auth ui flags', () => {
const state = {
currentUser: { id: 1 },
};
mutations[types.CLEAR_USER](state);
expect(state.currentUser).toEqual({
id: null,
account_id: null,
accounts: [],
email: null,
name: null,
});
});
});
describe('#SET_CURRENT_USER_AVAILABILITY', () => {
const state = {
currentUser: {
id: 1,
accounts: [{ id: 1, availability_status: 'offline' }],
account_id: 1,
},
};
it('set availability status for current user', () => {
mutations[types.SET_CURRENT_USER_AVAILABILITY](state, 'online');
expect(state.currentUser.accounts[0].availability_status).toEqual(
'online'
);
});
});
});