vue-instantsearch
Version:
👀 Lightning-fast Algolia search for Vue apps
222 lines (185 loc) • 6.62 kB
JavaScript
/**
* @jest-environment @instantsearch/testutils/jest-environment-jsdom.ts
*/
import { mount, nextTick } from '../../../test/utils';
import { __setState } from '../../mixins/widget';
import Feeds from '../Feeds';
jest.mock('../../mixins/widget');
jest.mock('instantsearch.js/es/connectors/feeds/FeedContainer', () => ({
createFeedContainer: jest.fn(feedID => ({ __feedID: feedID })),
}));
describe('AisFeeds', () => {
beforeEach(() => {
__setState(null);
});
it('passes arguments to connector', () => {
const transformFeeds = feeds => feeds;
const wrapper = mount(Feeds, {
propsData: {
isolated: false,
transformFeeds,
},
});
expect(wrapper.vm.widgetParams).toEqual({
isolated: false,
transformFeeds,
});
});
it('renders slot content in feed order and updates when order changes', async () => {
const parentIndex = {
addWidgets: jest.fn(),
removeWidgets: jest.fn(),
};
const wrapper = mount({
components: { Feeds },
template: `
<Feeds :isolated="false" v-slot="{ feedID }">
<div class="slot-feed">{{ feedID }}</div>
</Feeds>
`,
});
const feedsComponent = wrapper.findComponent(Feeds).vm;
feedsComponent.getParentIndex = () => parentIndex;
feedsComponent.state = { feedIDs: ['products', 'articles'] };
await nextTick();
const feeds = wrapper.findAll('.slot-feed');
expect(feeds).toHaveLength(2);
expect(feeds.at(0).text()).toBe('products');
expect(feeds.at(1).text()).toBe('articles');
feedsComponent.state = { feedIDs: ['articles', 'products'] };
await nextTick();
const reordered = wrapper.findAll('.slot-feed');
expect(reordered).toHaveLength(2);
expect(reordered.at(0).text()).toBe('articles');
expect(reordered.at(1).text()).toBe('products');
});
it('removes disappeared feeds with deferred parent cleanup', async () => {
jest.useFakeTimers();
const parentIndex = {
addWidgets: jest.fn(),
removeWidgets: jest.fn(),
};
const wrapper = mount({
components: { Feeds },
template: `
<Feeds :isolated="false" v-slot="{ feedID }">
<div class="slot-feed">{{ feedID }}</div>
</Feeds>
`,
});
const feedsComponent = wrapper.findComponent(Feeds).vm;
feedsComponent.getParentIndex = () => parentIndex;
feedsComponent.pendingRemovals = new Map();
feedsComponent.removalTimer = null;
feedsComponent.state = { feedIDs: ['products', 'articles'] };
await nextTick();
feedsComponent.state = { feedIDs: ['products'] };
await nextTick();
expect(wrapper.findAll('.slot-feed')).toHaveLength(1);
expect(wrapper.find('.slot-feed').text()).toBe('products');
expect(parentIndex.removeWidgets).not.toHaveBeenCalled();
jest.runAllTimers();
await nextTick();
expect(parentIndex.removeWidgets).toHaveBeenCalledTimes(1);
jest.useRealTimers();
});
it('reuses a pending feed container when the feed reappears before deferred cleanup', async () => {
jest.useFakeTimers();
const parentIndex = {
addWidgets: jest.fn(),
removeWidgets: jest.fn(),
};
const wrapper = mount({
components: { Feeds },
template: `
<Feeds :isolated="false" v-slot="{ feedID }">
<div class="slot-feed">{{ feedID }}</div>
</Feeds>
`,
});
const feedsComponent = wrapper.findComponent(Feeds).vm;
feedsComponent.getParentIndex = () => parentIndex;
feedsComponent.state = { feedIDs: ['products'] };
await nextTick();
const [firstFeedContainer] = parentIndex.addWidgets.mock.calls[0][0];
feedsComponent.state = { feedIDs: [] };
await nextTick();
expect(wrapper.findAll('.slot-feed')).toHaveLength(0);
expect(parentIndex.removeWidgets).not.toHaveBeenCalled();
feedsComponent.state = { feedIDs: ['products'] };
await nextTick();
expect(parentIndex.addWidgets).toHaveBeenCalledTimes(1);
expect(feedsComponent.feedContainers.get('products')).toStrictEqual(
firstFeedContainer
);
expect(wrapper.findAll('.slot-feed')).toHaveLength(1);
jest.runAllTimers();
await nextTick();
expect(parentIndex.removeWidgets).not.toHaveBeenCalled();
expect(feedsComponent.feedContainers.get('products')).toStrictEqual(
firstFeedContainer
);
jest.useRealTimers();
});
it('scopes slot descendants to the matching feed container', async () => {
const parentIndex = {
addWidgets: jest.fn(),
removeWidgets: jest.fn(),
};
const ScopedFeed = {
name: 'ScopedFeed',
inject: ['$_ais_getParentIndex'],
props: {
feedid: {
type: String,
required: true,
},
},
template: `<div class="scoped-feed">{{ feedid }}:{{ $_ais_getParentIndex().__feedID }}</div>`,
};
const wrapper = mount({
components: { Feeds, ScopedFeed },
template: `
<Feeds :isolated="false" v-slot="{ feedID }">
<ScopedFeed :feedid="feedID" />
</Feeds>
`,
});
const feedsComponent = wrapper.findComponent(Feeds).vm;
feedsComponent.getParentIndex = () => parentIndex;
feedsComponent.state = { feedIDs: ['products', 'articles'] };
await nextTick();
const scoped = wrapper.findAll('.scoped-feed');
expect(scoped).toHaveLength(2);
expect(scoped.at(0).text()).toBe('products:products');
expect(scoped.at(1).text()).toBe('articles:articles');
});
it('mounts containers for all feedIDs even with empty slot content', async () => {
const parentIndex = {
addWidgets: jest.fn(),
removeWidgets: jest.fn(),
};
const wrapper = mount({
components: { Feeds },
template: `
<Feeds :isolated="false" v-slot="{ feedID }">
<div v-if="feedID === 'products'" class="slot-feed">{{ feedID }}</div>
</Feeds>
`,
});
const feedsComponent = wrapper.findComponent(Feeds).vm;
feedsComponent.getParentIndex = () => parentIndex;
feedsComponent.state = { feedIDs: ['products', 'articles'] };
await nextTick();
expect(wrapper.findAll('.slot-feed')).toHaveLength(1);
expect(wrapper.find('.slot-feed').text()).toBe('products');
expect(parentIndex.addWidgets).toHaveBeenCalledTimes(1);
expect(parentIndex.addWidgets.mock.calls[0][0]).toHaveLength(2);
expect(parentIndex.addWidgets.mock.calls[0][0][0].__feedID).toBe(
'products'
);
expect(parentIndex.addWidgets.mock.calls[0][0][1].__feedID).toBe(
'articles'
);
});
});