diff --git a/components/index.js b/components/index.js
index 535379fcef..dc581a4e6e 100644
--- a/components/index.js
+++ b/components/index.js
@@ -73,6 +73,8 @@ import { default as message } from './message';
import { default as Menu } from './menu';
+import { default as Mentions } from './mentions';
+
import { default as Modal } from './modal';
import { default as notification } from './notification';
@@ -171,6 +173,7 @@ const components = [
List,
LocaleProvider,
Menu,
+ Mentions,
Modal,
Pagination,
Popconfirm,
@@ -258,6 +261,7 @@ export {
List,
LocaleProvider,
Menu,
+ Mentions,
Modal,
Pagination,
Popconfirm,
diff --git a/components/mentions/__test__/__snapshots__/demo.test.js.snap b/components/mentions/__test__/__snapshots__/demo.test.js.snap
new file mode 100644
index 0000000000..f34248c65c
--- /dev/null
+++ b/components/mentions/__test__/__snapshots__/demo.test.js.snap
@@ -0,0 +1,46 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders ./components/mentions/demo/async.md correctly 1`] = `
`;
+
+exports[`renders ./components/mentions/demo/basic.md correctly 1`] = ``;
+
+exports[`renders ./components/mentions/demo/form.md correctly 1`] = `
+
+`;
+
+exports[`renders ./components/mentions/demo/placement.md correctly 1`] = ``;
+
+exports[`renders ./components/mentions/demo/prefix.md correctly 1`] = ``;
+
+exports[`renders ./components/mentions/demo/readonly.md correctly 1`] = `
+
+`;
diff --git a/components/mentions/__test__/demo.test.js b/components/mentions/__test__/demo.test.js
new file mode 100644
index 0000000000..1946e55650
--- /dev/null
+++ b/components/mentions/__test__/demo.test.js
@@ -0,0 +1,3 @@
+import demoTest from '../../../tests/shared/demoTest';
+
+demoTest('mentions');
diff --git a/components/mentions/__test__/index.test.js b/components/mentions/__test__/index.test.js
new file mode 100644
index 0000000000..a9625d44df
--- /dev/null
+++ b/components/mentions/__test__/index.test.js
@@ -0,0 +1,91 @@
+import { mount } from '@vue/test-utils';
+import Vue from 'vue';
+import Mentions from '..';
+import { asyncExpect } from '@/tests/utils';
+import focusTest from '../../../tests/shared/focusTest';
+
+const { getMentions } = Mentions;
+
+function $$(className) {
+ return document.body.querySelectorAll(className);
+}
+
+function triggerInput(wrapper, text = '') {
+ wrapper.find('textarea').element.value = text;
+ wrapper.find('textarea').element.selectionStart = text.length;
+ wrapper.find('textarea').trigger('keydown');
+ wrapper.find('textarea').trigger('change');
+ wrapper.find('textarea').trigger('keyup');
+}
+
+describe('Mentions', () => {
+ beforeAll(() => {
+ jest.useFakeTimers();
+ });
+
+ afterAll(() => {
+ jest.useRealTimers();
+ });
+
+ it('getMentions', () => {
+ const mentions = getMentions('@light #bamboo cat', { prefix: ['@', '#'] });
+ expect(mentions).toEqual([
+ {
+ prefix: '@',
+ value: 'light',
+ },
+ {
+ prefix: '#',
+ value: 'bamboo',
+ },
+ ]);
+ });
+
+ it('focus', () => {
+ const onFocus = jest.fn();
+ const onBlur = jest.fn();
+
+ const wrapper = mount({
+ render() {
+ return ;
+ },
+ });
+ wrapper.find('textarea').trigger('focus');
+ expect(wrapper.find('.ant-mentions').classes('ant-mentions-focused')).toBeTruthy();
+ expect(onFocus).toHaveBeenCalled();
+
+ wrapper.find('textarea').trigger('blur');
+ jest.runAllTimers();
+ expect(wrapper.classes()).not.toContain('ant-mentions-focused');
+ expect(onBlur).toHaveBeenCalled();
+ });
+
+ it('loading', done => {
+ const wrapper = mount(
+ {
+ render() {
+ return ;
+ },
+ },
+ { sync: false },
+ );
+ triggerInput(wrapper, '@');
+ Vue.nextTick(() => {
+ mount(
+ {
+ render() {
+ return wrapper.find({ name: 'Trigger' }).vm.getComponent();
+ },
+ },
+ { sync: false },
+ );
+ Vue.nextTick(() => {
+ expect($$('.ant-mentions-dropdown-menu-item').length).toBeTruthy();
+ expect($$('.ant-spin')).toBeTruthy();
+ done();
+ });
+ });
+ });
+
+ focusTest(Mentions);
+});
diff --git a/components/mentions/demo/async.md b/components/mentions/demo/async.md
new file mode 100644
index 0000000000..747db1c428
--- /dev/null
+++ b/components/mentions/demo/async.md
@@ -0,0 +1,65 @@
+
+#### 异步加载
+匹配内容列表为异步返回时。
+
+
+
+#### Asynchronous loading
+async.
+
+
+```tpl
+
+
+
+
+ {{login}}
+
+
+
+
+
+