Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions packages/vant/src/lazyload/test/index.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { h, isReactive, nextTick } from 'vue';
import { mount } from '../../../test';
import createLazyComponent from '../vue-lazyload/lazy-component';
import createLazyImage from '../vue-lazyload/lazy-image';

const createLazyManager = (overrides = {}) => ({
options: {
attempt: 3,
preLoad: 1,
silent: true,
},
addLazyBox: rs.fn(),
removeComponent: rs.fn(),
lazyLoadHandler: rs.fn(),
valueFormatter: (src) => ({
src,
loading: `loading-${src}`,
error: `error-${src}`,
}),
...overrides,
});

test('LazyComponent should preserve the public component instance', async () => {
const lazy = createLazyManager();
const wrapper = mount(createLazyComponent(lazy), {
slots: {
default: () => h('span', 'content'),
},
});
const vm = wrapper.vm;
const lazyBox = lazy.addLazyBox.mock.calls[0][0];

expect(lazy.addLazyBox).toHaveBeenCalledTimes(1);
expect(lazyBox.$el).toBe(wrapper.element);
expect(lazyBox.el).toBe(wrapper.element);
expect(lazyBox.load).toBe(vm.load);
expect(vm.el).toBe(wrapper.element);
expect(vm.show).toBe(false);
expect(vm.state).toEqual({ loaded: false });
expect(vm.checkInView).toBeTypeOf('function');
expect(vm.load).toBeTypeOf('function');
expect(vm.destroy).toBeTypeOf('function');

vm.load();
await nextTick();

expect(vm.show).toBe(true);
expect(vm.state.loaded).toBe(true);

const payload = wrapper.emitted('show')[0][0];
expect(payload.show).toBe(true);
expect(payload.state.loaded).toBe(true);
expect(payload.load).toBe(vm.load);
expect(payload.checkInView).toBe(vm.checkInView);
expect(wrapper.html()).toBe('<div><span>content</span></div>');

wrapper.unmount();
expect(lazy.removeComponent.mock.calls[0][0]).toBe(lazyBox);
});

test('LazyImage should preserve public state and re-observe on src change', async () => {
const observer = {
observe: rs.fn(),
};
const lazy = createLazyManager({ observer });
const wrapper = mount(createLazyImage(lazy), {
props: {
src: 'a.png',
},
});
const vm = wrapper.vm;
const lazyBox = lazy.addLazyBox.mock.calls[0][0];

expect(lazy.addLazyBox).toHaveBeenCalledTimes(1);
expect(lazyBox.$el).toBe(wrapper.element);
expect(lazyBox.el).toBe(wrapper.element);
expect(lazyBox.load).toBe(vm.load);
expect(vm.el).toBe(wrapper.element);
expect(vm.src).toBe('a.png');
expect(vm.renderSrc).toBe('loading-a.png');
expect(vm.options.src).toBe('a.png');
expect(isReactive(vm.options)).toBe(true);
expect(isReactive(vm.state)).toBe(true);
expect(vm.init).toBeTypeOf('function');
expect(vm.checkInView).toBeTypeOf('function');
expect(vm.load).toBeTypeOf('function');

await wrapper.setProps({ src: 'b.png' });

expect(lazy.addLazyBox).toHaveBeenCalledTimes(1);
expect(lazy.lazyLoadHandler).toHaveBeenCalledTimes(2);
expect(observer.observe).toHaveBeenCalledTimes(1);
expect(observer.observe).toHaveBeenCalledWith(wrapper.element);
expect(vm.src).toBe('b.png');
expect(vm.renderSrc).toBe('loading-b.png');
expect(vm.options.src).toBe('b.png');
expect(wrapper.attributes('src')).toBe('loading-b.png');

wrapper.unmount();
expect(lazy.removeComponent.mock.calls[0][0]).toBe(lazyBox);
});
113 changes: 63 additions & 50 deletions packages/vant/src/lazyload/vue-lazyload/lazy-component.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,66 +3,79 @@
* license at https://github.com/hilongjw/vue-lazyload/blob/master/LICENSE
*/

import { h } from 'vue';
import {
h,
ref,
reactive,
defineComponent,
getCurrentInstance,
onMounted,
onBeforeUnmount,
} from 'vue';
import { inBrowser, useRect } from '@vant/use';

export default (lazy) => ({
props: {
tag: {
type: String,
default: 'div',
export default (lazy) =>
defineComponent({
props: {
tag: {
type: String,
default: 'div',
},
},
},

emits: ['show'],

render() {
return h(
this.tag,
this.show && this.$slots.default ? this.$slots.default() : null,
);
},
emits: ['show'],

data() {
return {
el: null,
state: {
setup(_, { emit }) {
const instance = getCurrentInstance();
const el = ref(null);
const show = ref(false);
const state = reactive({
loaded: false,
},
show: false,
};
},
});

mounted() {
this.el = this.$el;
lazy.addLazyBox(this);
lazy.lazyLoadHandler();
},
const checkInView = () => {
const rect = useRect(instance.proxy.$el);
return (
inBrowser &&
rect.top < window.innerHeight * lazy.options.preLoad &&
rect.bottom > 0 &&
rect.left < window.innerWidth * lazy.options.preLoad &&
rect.right > 0
);
};

beforeUnmount() {
lazy.removeComponent(this);
},
const load = () => {
show.value = true;
state.loaded = true;
emit('show', instance.proxy);
};

methods: {
checkInView() {
const rect = useRect(this.$el);
return (
inBrowser &&
rect.top < window.innerHeight * lazy.options.preLoad &&
rect.bottom > 0 &&
rect.left < window.innerWidth * lazy.options.preLoad &&
rect.right > 0
);
},
const destroy = () => instance.proxy.$destroy;

onMounted(() => {
el.value = instance.proxy.$el;
lazy.addLazyBox(instance.proxy);
lazy.lazyLoadHandler();
});

load() {
this.show = true;
this.state.loaded = true;
this.$emit('show', this);
onBeforeUnmount(() => {
lazy.removeComponent(instance.proxy);
});

return {
el,
show,
state,
checkInView,
load,
destroy,
};
},

destroy() {
return this.$destroy;
render() {
return h(
this.tag,
this.show && this.$slots.default ? this.$slots.default() : null,
);
},
},
});
});
Loading