Files
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

91 lines
2.4 KiB
JavaScript

import { flushPromises, shallowMount } from '@vue/test-utils';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import PublicArticleSearch from '../components/PublicArticleSearch.vue';
import ArticlesAPI from '../api/article';
vi.mock('../api/article', () => ({
default: {
searchArticles: vi.fn(),
},
}));
describe('PublicArticleSearch', () => {
let originalPortalConfig;
const SearchSuggestionsStub = {
name: 'SearchSuggestions',
template: '<div />',
props: ['searchTerm'],
};
beforeEach(() => {
vi.useFakeTimers();
originalPortalConfig = window.portalConfig;
window.portalConfig = {
portalSlug: 'test-portal',
localeCode: 'en',
searchTranslations: {},
};
});
afterEach(() => {
vi.clearAllMocks();
vi.useRealTimers();
window.portalConfig = originalPortalConfig;
});
const buildWrapper = () =>
shallowMount(PublicArticleSearch, {
global: {
directives: {
onClickaway: () => {},
},
stubs: {
SearchSuggestions: SearchSuggestionsStub,
PublicSearchInput: true,
},
},
});
it('does not fetch or show suggestions for whitespace-only search terms', async () => {
const wrapper = buildWrapper();
wrapper.vm.searchResults = [{ id: 1 }];
wrapper.vm.showSearchBox = true;
wrapper.vm.onUpdateSearchTerm(' ');
await wrapper.vm.$nextTick();
vi.runAllTimers();
await flushPromises();
expect(ArticlesAPI.searchArticles).not.toHaveBeenCalled();
expect(wrapper.vm.searchResults).toEqual([]);
expect(wrapper.vm.shouldShowSearchBox).toBe(false);
expect(wrapper.vm.isLoading).toBe(false);
});
it('trims the search term before requesting articles', async () => {
ArticlesAPI.searchArticles.mockResolvedValue({ data: { payload: [] } });
const wrapper = buildWrapper();
wrapper.vm.onUpdateSearchTerm(' chatwoot ');
vi.runAllTimers();
await flushPromises();
expect(ArticlesAPI.searchArticles).toHaveBeenCalledWith(
'test-portal',
'en',
'chatwoot'
);
});
it('passes the trimmed search term to suggestions for highlighting', async () => {
const wrapper = buildWrapper();
wrapper.vm.onUpdateSearchTerm(' chatwoot ');
await wrapper.vm.$nextTick();
expect(
wrapper.findComponent(SearchSuggestionsStub).props('searchTerm')
).toBe('chatwoot');
});
});