refactor(Anchor): use composition api (#4054)
parent
1139aa5f87
commit
1877d66cc5
|
@ -1,12 +1,21 @@
|
||||||
import { defineComponent, inject, nextTick, provide } from 'vue';
|
import {
|
||||||
|
defineComponent,
|
||||||
|
inject,
|
||||||
|
nextTick,
|
||||||
|
onBeforeUnmount,
|
||||||
|
onMounted,
|
||||||
|
onUpdated,
|
||||||
|
provide,
|
||||||
|
reactive,
|
||||||
|
ref,
|
||||||
|
getCurrentInstance,
|
||||||
|
} from 'vue';
|
||||||
import PropTypes from '../_util/vue-types';
|
import PropTypes from '../_util/vue-types';
|
||||||
import classNames from '../_util/classNames';
|
import classNames from '../_util/classNames';
|
||||||
import addEventListener from '../vc-util/Dom/addEventListener';
|
import addEventListener from '../vc-util/Dom/addEventListener';
|
||||||
import Affix from '../affix';
|
import Affix from '../affix';
|
||||||
import scrollTo from '../_util/scrollTo';
|
import scrollTo from '../_util/scrollTo';
|
||||||
import getScroll from '../_util/getScroll';
|
import getScroll from '../_util/getScroll';
|
||||||
import { findDOMNode } from '../_util/props-util';
|
|
||||||
import BaseMixin from '../_util/BaseMixin';
|
|
||||||
import { defaultConfigProvider } from '../config-provider';
|
import { defaultConfigProvider } from '../config-provider';
|
||||||
|
|
||||||
function getDefaultContainer() {
|
function getDefaultContainer() {
|
||||||
|
@ -77,76 +86,26 @@ export interface AnchorState {
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'AAnchor',
|
name: 'AAnchor',
|
||||||
mixins: [BaseMixin],
|
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
props: AnchorProps,
|
props: AnchorProps,
|
||||||
emits: ['change', 'click'],
|
emits: ['change', 'click'],
|
||||||
setup() {
|
setup(props, { emit, attrs, slots }) {
|
||||||
return {
|
const configProvider = inject('configProvider', defaultConfigProvider);
|
||||||
configProvider: inject('configProvider', defaultConfigProvider),
|
const instance = getCurrentInstance();
|
||||||
};
|
const inkNodeRef = ref();
|
||||||
},
|
const anchorRef = ref();
|
||||||
data() {
|
const state = reactive<AnchorState>({
|
||||||
// this.links = [];
|
|
||||||
// this.sPrefixCls = '';
|
|
||||||
return {
|
|
||||||
activeLink: null,
|
activeLink: null,
|
||||||
links: [],
|
links: [],
|
||||||
sPrefixCls: '',
|
sPrefixCls: '',
|
||||||
scrollContainer: null,
|
scrollContainer: null,
|
||||||
scrollEvent: null,
|
scrollEvent: null,
|
||||||
animating: false,
|
animating: false,
|
||||||
} as AnchorState;
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
provide('antAnchor', {
|
|
||||||
registerLink: (link: string) => {
|
|
||||||
if (!this.links.includes(link)) {
|
|
||||||
this.links.push(link);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
unregisterLink: (link: string) => {
|
|
||||||
const index = this.links.indexOf(link);
|
|
||||||
if (index !== -1) {
|
|
||||||
this.links.splice(index, 1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
$data: this.$data,
|
|
||||||
scrollTo: this.handleScrollTo,
|
|
||||||
} as AntAnchor);
|
|
||||||
provide('antAnchorContext', this);
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
nextTick(() => {
|
|
||||||
const { getContainer } = this;
|
|
||||||
this.scrollContainer = getContainer();
|
|
||||||
this.scrollEvent = addEventListener(this.scrollContainer, 'scroll', this.handleScroll);
|
|
||||||
this.handleScroll();
|
|
||||||
});
|
});
|
||||||
},
|
|
||||||
updated() {
|
// func...
|
||||||
nextTick(() => {
|
const getCurrentActiveLink = (offsetTop = 0, bounds = 5) => {
|
||||||
if (this.scrollEvent) {
|
const { getCurrentAnchor } = props;
|
||||||
const { getContainer } = this;
|
|
||||||
const currentContainer = getContainer();
|
|
||||||
if (this.scrollContainer !== currentContainer) {
|
|
||||||
this.scrollContainer = currentContainer;
|
|
||||||
this.scrollEvent.remove();
|
|
||||||
this.scrollEvent = addEventListener(this.scrollContainer, 'scroll', this.handleScroll);
|
|
||||||
this.handleScroll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.updateInk();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
beforeUnmount() {
|
|
||||||
if (this.scrollEvent) {
|
|
||||||
this.scrollEvent.remove();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getCurrentActiveLink(offsetTop = 0, bounds = 5) {
|
|
||||||
const { getCurrentAnchor } = this;
|
|
||||||
|
|
||||||
if (typeof getCurrentAnchor === 'function') {
|
if (typeof getCurrentAnchor === 'function') {
|
||||||
return getCurrentAnchor();
|
return getCurrentAnchor();
|
||||||
|
@ -157,9 +116,9 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
const linkSections: Array<Section> = [];
|
const linkSections: Array<Section> = [];
|
||||||
const { getContainer } = this;
|
const { getContainer } = props;
|
||||||
const container = getContainer();
|
const container = getContainer();
|
||||||
this.links.forEach(link => {
|
state.links.forEach(link => {
|
||||||
const sharpLinkMatch = sharpMatcherRegx.exec(link.toString());
|
const sharpLinkMatch = sharpMatcherRegx.exec(link.toString());
|
||||||
if (!sharpLinkMatch) {
|
if (!sharpLinkMatch) {
|
||||||
return;
|
return;
|
||||||
|
@ -181,12 +140,18 @@ export default defineComponent({
|
||||||
return maxSection.link;
|
return maxSection.link;
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
},
|
};
|
||||||
|
const setCurrentActiveLink = (link: string) => {
|
||||||
|
const { activeLink } = state;
|
||||||
|
if (activeLink !== link) {
|
||||||
|
state.activeLink = link;
|
||||||
|
emit('change', link);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleScrollTo = (link: string) => {
|
||||||
|
const { offsetTop, getContainer, targetOffset } = props;
|
||||||
|
|
||||||
handleScrollTo(link: string) {
|
setCurrentActiveLink(link);
|
||||||
const { offsetTop, getContainer, targetOffset } = this;
|
|
||||||
|
|
||||||
this.setCurrentActiveLink(link);
|
|
||||||
const container = getContainer();
|
const container = getContainer();
|
||||||
const scrollTop = getScroll(container, true);
|
const scrollTop = getScroll(container, true);
|
||||||
const sharpLinkMatch = sharpMatcherRegx.exec(link);
|
const sharpLinkMatch = sharpMatcherRegx.exec(link);
|
||||||
|
@ -201,99 +166,129 @@ export default defineComponent({
|
||||||
const eleOffsetTop = getOffsetTop(targetElement, container);
|
const eleOffsetTop = getOffsetTop(targetElement, container);
|
||||||
let y = scrollTop + eleOffsetTop;
|
let y = scrollTop + eleOffsetTop;
|
||||||
y -= targetOffset !== undefined ? targetOffset : offsetTop || 0;
|
y -= targetOffset !== undefined ? targetOffset : offsetTop || 0;
|
||||||
this.animating = true;
|
state.animating = true;
|
||||||
|
|
||||||
scrollTo(y, {
|
scrollTo(y, {
|
||||||
callback: () => {
|
callback: () => {
|
||||||
this.animating = false;
|
state.animating = false;
|
||||||
},
|
},
|
||||||
getContainer,
|
getContainer,
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
setCurrentActiveLink(link: string) {
|
const handleScroll = () => {
|
||||||
const { activeLink } = this;
|
if (state.animating) {
|
||||||
|
|
||||||
if (activeLink !== link) {
|
|
||||||
this.setState({
|
|
||||||
activeLink: link,
|
|
||||||
});
|
|
||||||
this.$emit('change', link);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
handleScroll() {
|
|
||||||
if (this.animating) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { offsetTop, bounds, targetOffset } = this;
|
const { offsetTop, bounds, targetOffset } = props;
|
||||||
const currentActiveLink = this.getCurrentActiveLink(
|
const currentActiveLink = getCurrentActiveLink(
|
||||||
targetOffset !== undefined ? targetOffset : offsetTop || 0,
|
targetOffset !== undefined ? targetOffset : offsetTop || 0,
|
||||||
bounds,
|
bounds,
|
||||||
);
|
);
|
||||||
this.setCurrentActiveLink(currentActiveLink);
|
setCurrentActiveLink(currentActiveLink);
|
||||||
},
|
};
|
||||||
|
|
||||||
updateInk() {
|
const updateInk = () => {
|
||||||
if (typeof document === 'undefined') {
|
if (typeof document === 'undefined') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { sPrefixCls } = this;
|
const { sPrefixCls } = state;
|
||||||
const linkNode = findDOMNode(this).getElementsByClassName(
|
const linkNode = anchorRef.value.getElementsByClassName(`${sPrefixCls}-link-title-active`)[0];
|
||||||
`${sPrefixCls}-link-title-active`,
|
|
||||||
)[0];
|
|
||||||
if (linkNode) {
|
if (linkNode) {
|
||||||
(this.$refs.inkNode as HTMLElement).style.top = `${linkNode.offsetTop +
|
(inkNodeRef.value as HTMLElement).style.top = `${linkNode.offsetTop +
|
||||||
linkNode.clientHeight / 2 -
|
linkNode.clientHeight / 2 -
|
||||||
4.5}px`;
|
4.5}px`;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
prefixCls: customizePrefixCls,
|
|
||||||
offsetTop,
|
|
||||||
affix,
|
|
||||||
showInkInFixed,
|
|
||||||
activeLink,
|
|
||||||
$slots,
|
|
||||||
getContainer,
|
|
||||||
} = this;
|
|
||||||
const getPrefixCls = this.configProvider.getPrefixCls;
|
|
||||||
const prefixCls = getPrefixCls('anchor', customizePrefixCls);
|
|
||||||
this.sPrefixCls = prefixCls;
|
|
||||||
|
|
||||||
const inkClass = classNames(`${prefixCls}-ink-ball`, {
|
|
||||||
visible: activeLink,
|
|
||||||
});
|
|
||||||
|
|
||||||
const wrapperClass = classNames(this.wrapperClass, `${prefixCls}-wrapper`);
|
|
||||||
|
|
||||||
const anchorClass = classNames(prefixCls, {
|
|
||||||
fixed: !affix && !showInkInFixed,
|
|
||||||
});
|
|
||||||
|
|
||||||
const wrapperStyle = {
|
|
||||||
maxHeight: offsetTop ? `calc(100vh - ${offsetTop}px)` : '100vh',
|
|
||||||
...this.wrapperStyle,
|
|
||||||
};
|
};
|
||||||
const anchorContent = (
|
|
||||||
<div class={wrapperClass} style={wrapperStyle}>
|
|
||||||
<div class={anchorClass}>
|
|
||||||
<div class={`${prefixCls}-ink`}>
|
|
||||||
<span class={inkClass} ref="inkNode" />
|
|
||||||
</div>
|
|
||||||
{$slots.default?.()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return !affix ? (
|
// provide data
|
||||||
anchorContent
|
provide('antAnchor', {
|
||||||
) : (
|
registerLink: (link: string) => {
|
||||||
<Affix {...this.$attrs} offsetTop={offsetTop} target={getContainer}>
|
if (!state.links.includes(link)) {
|
||||||
{anchorContent}
|
state.links.push(link);
|
||||||
</Affix>
|
}
|
||||||
);
|
},
|
||||||
|
unregisterLink: (link: string) => {
|
||||||
|
const index = state.links.indexOf(link);
|
||||||
|
if (index !== -1) {
|
||||||
|
state.links.splice(index, 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
$data: state,
|
||||||
|
scrollTo: handleScrollTo,
|
||||||
|
} as AntAnchor);
|
||||||
|
provide('antAnchorContext', instance);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
const { getContainer } = props;
|
||||||
|
state.scrollContainer = getContainer();
|
||||||
|
state.scrollEvent = addEventListener(state.scrollContainer, 'scroll', handleScroll);
|
||||||
|
handleScroll();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (state.scrollEvent) {
|
||||||
|
state.scrollEvent.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
onUpdated(() => {
|
||||||
|
if (state.scrollEvent) {
|
||||||
|
const { getContainer } = props;
|
||||||
|
const currentContainer = getContainer();
|
||||||
|
if (state.scrollContainer !== currentContainer) {
|
||||||
|
state.scrollContainer = currentContainer;
|
||||||
|
state.scrollEvent.remove();
|
||||||
|
state.scrollEvent = addEventListener(state.scrollContainer, 'scroll', handleScroll);
|
||||||
|
handleScroll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateInk();
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
const {
|
||||||
|
prefixCls: customizePrefixCls,
|
||||||
|
offsetTop,
|
||||||
|
affix,
|
||||||
|
showInkInFixed,
|
||||||
|
getContainer,
|
||||||
|
} = props;
|
||||||
|
const getPrefixCls = configProvider.getPrefixCls;
|
||||||
|
const prefixCls = getPrefixCls('anchor', customizePrefixCls);
|
||||||
|
state.sPrefixCls = prefixCls;
|
||||||
|
|
||||||
|
const inkClass = classNames(`${prefixCls}-ink-ball`, {
|
||||||
|
visible: state.activeLink,
|
||||||
|
});
|
||||||
|
|
||||||
|
const wrapperClass = classNames(props.wrapperClass, `${prefixCls}-wrapper`);
|
||||||
|
|
||||||
|
const anchorClass = classNames(prefixCls, {
|
||||||
|
fixed: !affix && !showInkInFixed,
|
||||||
|
});
|
||||||
|
|
||||||
|
const wrapperStyle = {
|
||||||
|
maxHeight: offsetTop ? `calc(100vh - ${offsetTop}px)` : '100vh',
|
||||||
|
...props.wrapperStyle,
|
||||||
|
};
|
||||||
|
const anchorContent = (
|
||||||
|
<div class={wrapperClass} style={wrapperStyle} ref={anchorRef}>
|
||||||
|
<div class={anchorClass}>
|
||||||
|
<div class={`${prefixCls}-ink`}>
|
||||||
|
<span class={inkClass} ref={inkNodeRef} />
|
||||||
|
</div>
|
||||||
|
{slots.default?.()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return !affix ? (
|
||||||
|
anchorContent
|
||||||
|
) : (
|
||||||
|
<Affix {...attrs} offsetTop={offsetTop} target={getContainer}>
|
||||||
|
{anchorContent}
|
||||||
|
</Affix>
|
||||||
|
);
|
||||||
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
import { ComponentPublicInstance, defineComponent, inject, nextTick } from 'vue';
|
import {
|
||||||
|
ComponentInternalInstance,
|
||||||
|
defineComponent,
|
||||||
|
inject,
|
||||||
|
nextTick,
|
||||||
|
onBeforeUnmount,
|
||||||
|
onMounted,
|
||||||
|
watch,
|
||||||
|
} from 'vue';
|
||||||
import PropTypes from '../_util/vue-types';
|
import PropTypes from '../_util/vue-types';
|
||||||
import { getComponent } from '../_util/props-util';
|
import { getPropsSlot } from '../_util/props-util';
|
||||||
import classNames from '../_util/classNames';
|
import classNames from '../_util/classNames';
|
||||||
import { defaultConfigProvider } from '../config-provider';
|
import { defaultConfigProvider } from '../config-provider';
|
||||||
import { AntAnchor } from './Anchor';
|
import { AntAnchor } from './Anchor';
|
||||||
|
@ -18,72 +26,72 @@ const AnchorLinkProps = {
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'AAnchorLink',
|
name: 'AAnchorLink',
|
||||||
props: AnchorLinkProps,
|
props: AnchorLinkProps,
|
||||||
setup() {
|
setup(props, { slots }) {
|
||||||
return {
|
const antAnchor = inject('antAnchor', {
|
||||||
antAnchor: inject('antAnchor', {
|
registerLink: noop,
|
||||||
registerLink: noop,
|
unregisterLink: noop,
|
||||||
unregisterLink: noop,
|
scrollTo: noop,
|
||||||
scrollTo: noop,
|
$data: {},
|
||||||
$data: {},
|
} as AntAnchor);
|
||||||
} as AntAnchor),
|
const antAnchorContext = inject('antAnchorContext', {}) as ComponentInternalInstance;
|
||||||
antAnchorContext: inject('antAnchorContext', {}) as ComponentPublicInstance,
|
const configProvider = inject('configProvider', defaultConfigProvider);
|
||||||
configProvider: inject('configProvider', defaultConfigProvider),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
href(val, oldVal) {
|
|
||||||
nextTick(() => {
|
|
||||||
this.antAnchor.unregisterLink(oldVal);
|
|
||||||
this.antAnchor.registerLink(val);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
const handleClick = (e: Event) => {
|
||||||
this.antAnchor.registerLink(this.href);
|
// antAnchor.scrollTo(props.href);
|
||||||
},
|
const { scrollTo } = antAnchor;
|
||||||
|
const { href, title } = props;
|
||||||
beforeUnmount() {
|
if (antAnchorContext.emit) {
|
||||||
this.antAnchor.unregisterLink(this.href);
|
antAnchorContext.emit('click', e, { title, href });
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
handleClick(e: Event) {
|
|
||||||
this.antAnchor.scrollTo(this.href);
|
|
||||||
const { scrollTo } = this.antAnchor;
|
|
||||||
const { href, title } = this.$props;
|
|
||||||
if (this.antAnchorContext.$emit) {
|
|
||||||
this.antAnchorContext.$emit('click', e, { title, href });
|
|
||||||
}
|
}
|
||||||
scrollTo(href);
|
scrollTo(href);
|
||||||
},
|
};
|
||||||
},
|
|
||||||
render() {
|
|
||||||
const { prefixCls: customizePrefixCls, href, $slots, target } = this;
|
|
||||||
|
|
||||||
const getPrefixCls = this.configProvider.getPrefixCls;
|
watch(
|
||||||
const prefixCls = getPrefixCls('anchor', customizePrefixCls);
|
() => props.href,
|
||||||
|
(val, oldVal) => {
|
||||||
const title = getComponent(this, 'title');
|
nextTick(() => {
|
||||||
const active = this.antAnchor.$data.activeLink === href;
|
antAnchor.unregisterLink(oldVal);
|
||||||
const wrapperClassName = classNames(`${prefixCls}-link`, {
|
antAnchor.registerLink(val);
|
||||||
[`${prefixCls}-link-active`]: active,
|
});
|
||||||
});
|
},
|
||||||
const titleClassName = classNames(`${prefixCls}-link-title`, {
|
|
||||||
[`${prefixCls}-link-title-active`]: active,
|
|
||||||
});
|
|
||||||
return (
|
|
||||||
<div class={wrapperClassName}>
|
|
||||||
<a
|
|
||||||
class={titleClassName}
|
|
||||||
href={href}
|
|
||||||
title={typeof title === 'string' ? title : ''}
|
|
||||||
target={target}
|
|
||||||
onClick={this.handleClick}
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</a>
|
|
||||||
{$slots.default?.()}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
antAnchor.registerLink(props.href);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
antAnchor.unregisterLink(props.href);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
const { prefixCls: customizePrefixCls, href, target } = props;
|
||||||
|
|
||||||
|
const getPrefixCls = configProvider.getPrefixCls;
|
||||||
|
const prefixCls = getPrefixCls('anchor', customizePrefixCls);
|
||||||
|
|
||||||
|
const title = getPropsSlot(slots, props, 'title');
|
||||||
|
const active = antAnchor.$data.activeLink === href;
|
||||||
|
const wrapperClassName = classNames(`${prefixCls}-link`, {
|
||||||
|
[`${prefixCls}-link-active`]: active,
|
||||||
|
});
|
||||||
|
const titleClassName = classNames(`${prefixCls}-link-title`, {
|
||||||
|
[`${prefixCls}-link-title-active`]: active,
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div class={wrapperClassName}>
|
||||||
|
<a
|
||||||
|
class={titleClassName}
|
||||||
|
href={href}
|
||||||
|
title={typeof title === 'string' ? title : ''}
|
||||||
|
target={target}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</a>
|
||||||
|
{slots.default?.()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import * as Vue from 'vue';
|
import { ref } from 'vue';
|
||||||
import { asyncExpect } from '@/tests/utils';
|
|
||||||
import Anchor from '..';
|
import Anchor from '..';
|
||||||
|
|
||||||
const { Link } = Anchor;
|
const { Link } = Anchor;
|
||||||
|
@ -9,13 +8,20 @@ let idCounter = 0;
|
||||||
const getHashUrl = () => `Anchor-API-${idCounter++}`;
|
const getHashUrl = () => `Anchor-API-${idCounter++}`;
|
||||||
|
|
||||||
describe('Anchor Render', () => {
|
describe('Anchor Render', () => {
|
||||||
it('Anchor render perfectly', done => {
|
it('Anchor render perfectly', async done => {
|
||||||
const hash = getHashUrl();
|
const hash = getHashUrl();
|
||||||
|
const anchor = ref(null);
|
||||||
|
const activeLink = ref(null);
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
{
|
{
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Anchor ref="anchor">
|
<Anchor
|
||||||
|
ref={anchor}
|
||||||
|
onChange={current => {
|
||||||
|
activeLink.value = current;
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Link href={`#${hash}`} title={hash} />
|
<Link href={`#${hash}`} title={hash} />
|
||||||
</Anchor>
|
</Anchor>
|
||||||
);
|
);
|
||||||
|
@ -23,22 +29,28 @@ describe('Anchor Render', () => {
|
||||||
},
|
},
|
||||||
{ sync: false },
|
{ sync: false },
|
||||||
);
|
);
|
||||||
Vue.nextTick(() => {
|
|
||||||
|
wrapper.vm.$nextTick(() => {
|
||||||
wrapper.find(`a[href="#${hash}`).trigger('click');
|
wrapper.find(`a[href="#${hash}`).trigger('click');
|
||||||
wrapper.vm.$refs.anchor.handleScroll();
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
expect(wrapper.vm.$refs.anchor.$data.activeLink).not.toBe(null);
|
expect(activeLink.value).not.toBe(hash);
|
||||||
done();
|
done();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it('Anchor render perfectly for complete href - click', async done => {
|
||||||
it('Anchor render perfectly for complete href - click', done => {
|
const currentActiveLink = ref(null);
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
{
|
{
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Anchor ref="anchor">
|
<Anchor
|
||||||
|
ref="anchor"
|
||||||
|
onChange={current => {
|
||||||
|
currentActiveLink.value = current;
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Link href="http://www.example.com/#API" title="API" />
|
<Link href="http://www.example.com/#API" title="API" />
|
||||||
</Anchor>
|
</Anchor>
|
||||||
);
|
);
|
||||||
|
@ -46,160 +58,163 @@ describe('Anchor Render', () => {
|
||||||
},
|
},
|
||||||
{ sync: false },
|
{ sync: false },
|
||||||
);
|
);
|
||||||
Vue.nextTick(() => {
|
|
||||||
|
wrapper.vm.$nextTick(() => {
|
||||||
wrapper.find('a[href="http://www.example.com/#API"]').trigger('click');
|
wrapper.find('a[href="http://www.example.com/#API"]').trigger('click');
|
||||||
expect(wrapper.vm.$refs.anchor.$data.activeLink).toBe('http://www.example.com/#API');
|
|
||||||
|
expect(currentActiveLink.value).toBe('http://www.example.com/#API');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
/*
|
||||||
it('Anchor render perfectly for complete href - scroll', done => {
|
it('Anchor render perfectly for complete href - scroll', done => {
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
{
|
{
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div id="API">Hello</div>
|
<div id="API">Hello</div>
|
||||||
<Anchor ref="anchor">
|
<Anchor ref="anchor">
|
||||||
<Link href="http://www.example.com/#API" title="API" />
|
<Link href="http://www.example.com/#API" title="API" />
|
||||||
</Anchor>
|
</Anchor>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
|
||||||
},
|
|
||||||
{ sync: false, attachTo: 'body' },
|
|
||||||
);
|
|
||||||
Vue.nextTick(() => {
|
|
||||||
wrapper.vm.$refs.anchor.handleScroll();
|
|
||||||
expect(wrapper.vm.$refs.anchor.$data.activeLink).toBe('http://www.example.com/#API');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Anchor render perfectly for complete href - scrollTo', async () => {
|
|
||||||
const scrollToSpy = jest.spyOn(window, 'scrollTo');
|
|
||||||
const wrapper = mount(
|
|
||||||
{
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div id="API">Hello</div>
|
|
||||||
<Anchor ref="anchor">
|
|
||||||
<Link href="##API" title="API" />
|
|
||||||
</Anchor>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ sync: false, attachTo: 'body' },
|
|
||||||
);
|
|
||||||
await asyncExpect(() => {
|
|
||||||
wrapper.vm.$refs.anchor.handleScrollTo('##API');
|
|
||||||
expect(wrapper.vm.$refs.anchor.$data.activeLink).toBe('##API');
|
|
||||||
expect(scrollToSpy).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
await asyncExpect(() => {
|
|
||||||
expect(scrollToSpy).toHaveBeenCalled();
|
|
||||||
}, 1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should remove listener when unmount', async () => {
|
|
||||||
const wrapper = mount(
|
|
||||||
{
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Anchor ref="anchor">
|
|
||||||
<Link href="#API" title="API" />
|
|
||||||
</Anchor>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ sync: false, attachTo: 'body' },
|
|
||||||
);
|
|
||||||
await asyncExpect(() => {
|
|
||||||
const removeListenerSpy = jest.spyOn(wrapper.vm.$refs.anchor.scrollEvent, 'remove');
|
|
||||||
wrapper.unmount();
|
|
||||||
expect(removeListenerSpy).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should unregister link when unmount children', async () => {
|
|
||||||
const wrapper = mount(
|
|
||||||
{
|
|
||||||
props: {
|
|
||||||
showLink: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
render() {
|
{ sync: false, attachTo: 'body' },
|
||||||
return (
|
);
|
||||||
<Anchor ref="anchor">{this.showLink ? <Link href="#API" title="API" /> : null}</Anchor>
|
wrapper.vm.$nextTick(() => {
|
||||||
);
|
wrapper.vm.$refs.anchor.handleScroll();
|
||||||
},
|
expect(wrapper.vm.$refs.anchor.$data.activeLink).toBe('http://www.example.com/#API');
|
||||||
},
|
done();
|
||||||
{ sync: false, attachTo: 'body' },
|
});
|
||||||
);
|
|
||||||
await asyncExpect(() => {
|
|
||||||
expect(wrapper.vm.$refs.anchor.links).toEqual(['#API']);
|
|
||||||
wrapper.setProps({ showLink: false });
|
|
||||||
});
|
});
|
||||||
await asyncExpect(() => {
|
|
||||||
expect(wrapper.vm.$refs.anchor.links).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update links when link href update', async () => {
|
it('Anchor render perfectly for complete href - scrollTo', async () => {
|
||||||
const wrapper = mount(
|
const scrollToSpy = jest.spyOn(window, 'scrollTo');
|
||||||
{
|
const wrapper = mount(
|
||||||
props: ['href'],
|
{
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Anchor ref="anchor">
|
<div>
|
||||||
<Link href={this.href} title="API" />
|
<div id="API">Hello</div>
|
||||||
</Anchor>
|
<Anchor ref="anchor">
|
||||||
);
|
<Link href="##API" title="API" />
|
||||||
},
|
</Anchor>
|
||||||
},
|
</div>
|
||||||
{
|
);
|
||||||
sync: false,
|
},
|
||||||
attachTo: 'body',
|
},
|
||||||
props: {
|
{ sync: false, attachTo: 'body' },
|
||||||
href: '#API',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
await asyncExpect(() => {
|
|
||||||
expect(wrapper.vm.$refs.anchor.links).toEqual(['#API']);
|
|
||||||
wrapper.setProps({ href: '#API_1' });
|
|
||||||
});
|
|
||||||
await asyncExpect(() => {
|
|
||||||
expect(wrapper.vm.$refs.anchor.links).toEqual(['#API_1']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Anchor onClick event', () => {
|
|
||||||
let event;
|
|
||||||
let link;
|
|
||||||
const handleClick = (...arg) => ([event, link] = arg);
|
|
||||||
|
|
||||||
const href = '#API';
|
|
||||||
const title = 'API';
|
|
||||||
|
|
||||||
const wrapper = mount({
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Anchor ref="anchorRef" onClick={handleClick}>
|
|
||||||
<Link href={href} title={title} />
|
|
||||||
</Anchor>
|
|
||||||
);
|
);
|
||||||
},
|
await asyncExpect(() => {
|
||||||
});
|
wrapper.vm.$refs.anchor.handleScrollTo('##API');
|
||||||
|
expect(wrapper.vm.$refs.anchor.$data.activeLink).toBe('##API');
|
||||||
|
expect(scrollToSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
await asyncExpect(() => {
|
||||||
|
expect(scrollToSpy).toHaveBeenCalled();
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
wrapper.find(`a[href="${href}"]`).trigger('click');
|
it('should remove listener when unmount', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
{
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Anchor ref="anchor">
|
||||||
|
<Link href="#API" title="API" />
|
||||||
|
</Anchor>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ sync: false, attachTo: 'body' },
|
||||||
|
);
|
||||||
|
await asyncExpect(() => {
|
||||||
|
const removeListenerSpy = jest.spyOn(wrapper.vm.$refs.anchor.scrollEvent, 'remove');
|
||||||
|
wrapper.unmount();
|
||||||
|
expect(removeListenerSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
wrapper.vm.$refs.anchorRef.handleScroll();
|
it('should unregister link when unmount children', async () => {
|
||||||
expect(event).not.toBe(undefined);
|
const wrapper = mount(
|
||||||
expect(link).toEqual({ href, title });
|
{
|
||||||
});
|
props: {
|
||||||
|
showLink: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Anchor ref="anchor">{this.showLink ? <Link href="#API" title="API" /> : null}</Anchor>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ sync: false, attachTo: 'body' },
|
||||||
|
);
|
||||||
|
await asyncExpect(() => {
|
||||||
|
expect(wrapper.vm.$refs.anchor.links).toEqual(['#API']);
|
||||||
|
wrapper.setProps({ showLink: false });
|
||||||
|
});
|
||||||
|
await asyncExpect(() => {
|
||||||
|
expect(wrapper.vm.$refs.anchor.links).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update links when link href update', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
{
|
||||||
|
props: ['href'],
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Anchor ref="anchor">
|
||||||
|
<Link href={this.href} title="API" />
|
||||||
|
</Anchor>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sync: false,
|
||||||
|
attachTo: 'body',
|
||||||
|
props: {
|
||||||
|
href: '#API',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
await asyncExpect(() => {
|
||||||
|
expect(wrapper.vm.$refs.anchor.links).toEqual(['#API']);
|
||||||
|
wrapper.setProps({ href: '#API_1' });
|
||||||
|
});
|
||||||
|
await asyncExpect(() => {
|
||||||
|
expect(wrapper.vm.$refs.anchor.links).toEqual(['#API_1']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Anchor onClick event', () => {
|
||||||
|
let event;
|
||||||
|
let link;
|
||||||
|
const handleClick = (...arg) => ([event, link] = arg);
|
||||||
|
|
||||||
|
const href = '#API';
|
||||||
|
const title = 'API';
|
||||||
|
|
||||||
|
const anchorRef = Vue.ref(null);
|
||||||
|
|
||||||
|
const wrapper = mount({
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Anchor ref={anchorRef} onClick={handleClick}>
|
||||||
|
<Link href={href} title={title} />
|
||||||
|
</Anchor>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.find(`a[href="${href}"]`).trigger('click');
|
||||||
|
anchorRef.value.handleScroll();
|
||||||
|
expect(event).not.toBe(undefined);
|
||||||
|
expect(link).toEqual({ href, title });
|
||||||
|
}); */
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue