Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(banner): banner without title and no clickable #1069

Merged
merged 2 commits into from
Feb 27, 2023
Merged
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
25 changes: 11 additions & 14 deletions packages/x-components/src/__stubs__/banners-stubs.factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,35 @@ import { Banner } from '@empathyco/x-types';
*/
export function getBannersStub(): Banner[] {
return [
createBannerStub('1', 1),
createBannerStub('2', 3),
createBannerStub('3', 3),
createBannerStub('4', 4),
createBannerStub('5', 7),
createBannerStub('6', 9)
createBannerStub('1', { position: 1 }),
createBannerStub('2', { position: 3 }),
createBannerStub('3', { position: 3 }),
createBannerStub('4', { position: 4 }),
createBannerStub('5', { position: 7 }),
createBannerStub('6', { position: 9 })
];
}

/**
* Creates a banner with a "unique" identifier.
* Creates a banner.
*
* @param identifier - The banner identifier.
* @param position - The banner position (= row) inside the grid.
*
* @param banner - An optional object with fields to override the banner.
* @returns The banner.
*
* @internal
*/
export function createBannerStub(identifier: string, position?: number): Banner {
export function createBannerStub(identifier: string, banner?: Partial<Banner>): Banner {
return {
id: `xb-${identifier}`,
title: `Banner ${identifier}`,
url: `/banner/${identifier}`,
image: `xb-${identifier}.jpg`,
position,
modelName: 'Banner',
tagging: {
click: {
params: {},
url: ''
}
},
modelName: 'Banner'
...banner
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ describe('testing ItemsList component', () => {
const { wrapper } = renderItemsList({
scopedSlots: {
result: `<template #result="{ item }">Result: {{ item.name }}</template>`,
banner: `<template #banner="{ item }">Banner: {{ item.title }}</template>`,
banner: `<template #banner="{ item }">Banner: {{ item.id }}</template>`,
promoted: `<template #promoted="{ item }">Promoted: {{ item.title }}</template>`
},
items: [resultsStub[0], promotedsStub[0], bannersStub[0]]
Expand All @@ -87,7 +87,7 @@ describe('testing ItemsList component', () => {
);

expect(wrapper.find(getDataTestSelector('banners-list-item')).text()).toBe(
`Banner: ${bannersStub[0].title}`
`Banner: ${bannersStub[0].id}`
);

expect(wrapper.find(getDataTestSelector('promoteds-list-item')).text()).toBe(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,28 +44,26 @@ describe('testing Banner component', () => {
expect(getXComponentXModuleName(wrapper.vm)).toEqual('search');
});

it('renders the banner component', () => {
it('renders a banner component with title', () => {
const { wrapper } = renderBanner({
banner: {
modelName: 'Banner',
id: '12345',
url: 'https://empathy.co',
title: 'Search UIs',
image: 'https://empathy.co/x-components.jpg',
position: 1,
tagging: {
click: { url: 'https://track-things.com', params: {} }
}
}
banner: createBannerStub('banner', { title: 'Search UIs' })
});

expect(wrapper.get(getDataTestSelector('banner')).text()).toEqual('Search UIs');
});

it('renders a banner component without title', () => {
const { wrapper } = renderBanner({
banner: createBannerStub('banner')
});

expect(wrapper.get(getDataTestSelector('banner')).text()).toEqual('');
});

// eslint-disable-next-line max-len
it('emits UserClickedABanner when the user clicks in the left, middle or right button on the component', () => {
it('renders a banner which emits UserClickedABanner when the user clicks in the left, middle or right button on the component', () => {
CachedaCodes marked this conversation as resolved.
Show resolved Hide resolved
const listener = jest.fn();
const banner = createBannerStub('banner');
const banner = createBannerStub('banner', { url: 'https://empathy.co' });
const { wrapper } = renderBanner({ banner });
wrapper.vm.$x.on('UserClickedABanner').subscribe(listener);

Expand All @@ -80,6 +78,25 @@ describe('testing Banner component', () => {

expect(listener).toHaveBeenCalledTimes(3);
});

it('renders a banner which does not emits any event on click', () => {
const listener = jest.fn();
const { wrapper } = renderBanner({
banner: createBannerStub('banner', { title: 'Search UIs' })
});
wrapper.vm.$x.on('UserClickedABanner').subscribe(listener);

wrapper.trigger('click');
expect(listener).not.toHaveBeenCalled();

wrapper.trigger('click', { button: 1 });
expect(listener).not.toHaveBeenCalled();

wrapper.trigger('click', { button: 2 });
expect(listener).not.toHaveBeenCalled();

expect(listener).toHaveBeenCalledTimes(0);
});
});

interface RenderBannerOptions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,15 @@ describe('testing BannersList component', () => {
template: `
<BannersList>
<template #banner="{ item }">
<p data-test="banner-slot-overridden">Custom banner: {{ item.title }}</p>
<p data-test="banner-slot-overridden">Custom banner: {{ item.id }}</p>
</template>
</BannersList>`
});

expect(wrapper.classes('x-items-list')).toBe(true);
expect(wrapper.find(getDataTestSelector('banners-list-item')).exists()).toBe(true);
expect(wrapper.find(getDataTestSelector('banner-slot-overridden')).text()).toBe(
`Custom banner: ${getBanners()[0].title}`
`Custom banner: ${getBanners()[0].id}`
);
});

Expand Down
55 changes: 45 additions & 10 deletions packages/x-components/src/x-modules/search/components/banner.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
<template>
<a
@click="emitClickEvent"
@click.right="emitClickEvent"
@click.middle="emitClickEvent"
<component
:is="banner.url ? 'a' : 'figure'"
v-if="!imageFailed"
v-on="banner.url ? anchorEvents() : {}"
:href="banner.url"
class="x-banner"
data-test="banner"
>
<img :src="banner.image" class="x-banner__image" :alt="banner.title" />
<h2 class="x-banner__title">{{ banner.title }}</h2>
</a>
<img
@error="imageFailed = true"
:src="banner.image"
:alt="banner.title ? banner.title : 'Banner'"
class="x-banner__image"
data-test="banner-image"
/>
<h2 v-if="banner.title" class="x-banner__title">
{{ banner.title }}
</h2>
</component>
</template>

<script lang="ts">
Expand All @@ -18,11 +26,14 @@
import { Component, Prop } from 'vue-property-decorator';
import { xComponentMixin } from '../../../components/x-component.mixin';
import { searchXModule } from '../x-module';
/**

/**.
* A banner result is just an item that has been inserted into the search results to advertise
* something. Usually it is the first item in the grid or it can be placed in the middle of them
* and fill the whole row where appears. It just contains a link to the banner content, an image
* and a title.
* and fill the whole row where appears.
* The banner may be clickable or non-clickable depending on whether it has an associated URL
* or not. It contains an image and, optionally, a title. In case the image does not
* load due to an error the banner will not be rendered.
*
* @public
*/
Expand All @@ -38,6 +49,13 @@
@Prop({ required: true })
public banner!: BannerModel;

/**
* Flag to handle banner image errors.
*
* @public
*/
protected imageFailed = false;

/**
* Emits the banner click event.
*
Expand All @@ -46,6 +64,23 @@
protected emitClickEvent(): void {
this.$x.emit('UserClickedABanner', this.banner);
}

/**
* Returns the events supported by the anchor.
*
* @returns Events supported by the anchor.
*
* @internal
*/
protected anchorEvents(): Partial<{
[key in keyof GlobalEventHandlersEventMap]: () => void;
}> {
return {
click: () => this.emitClickEvent(),
auxclick: () => this.emitClickEvent(),
contextmenu: () => this.emitClickEvent()
};
}
}
</script>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,17 @@ Given('a results API with a promoted', () => {

Given('a results API with a banner', () => {
cy.intercept(searchEndpoint, req => {
req.reply(createSearchResponse({ banners: [createBannerStub('Banner')] }));
req.reply(
createSearchResponse({
banners: [
createBannerStub('Banner', {
title: 'Banner',
url: '/banner/Banner',
image: '/img/test-image-1.jpeg'
})
]
})
);
}).as('interceptedResults');
});

Expand Down
89 changes: 89 additions & 0 deletions packages/x-components/tests/unit/banner.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { mount } from 'cypress/vue2';
import { Banner as BannerModel } from '@empathyco/x-types';
import Banner from '../../src/x-modules/search/components/banner.vue';
import { createBannerStub } from '../../src/__stubs__/index';

/**
* Mounts a {@link Banner} component with the provided options.
*
* @param options - The options to render the component with.
* @returns An API to test the component.
*/
function mountBanner({
banner,
template = `
<div>
<Banner :banner="banner"/>
</div>
`
}: MountBannerOptions): MountBannerAPI {
cy.viewport(1920, 200);
mount({
components: {
Banner
},
data() {
return {
banner
};
},
template
});

return {
getBanner() {
return cy.getByDataTest('banner');
},
getBannerImage() {
return cy.getByDataTest('banner-image');
}
};
}

describe('testing Banner component', () => {
it('banner renders if the image loads', () => {
const { getBanner, getBannerImage } = mountBanner({
banner: createBannerStub('banner', {
title: 'Search UIs',
url: 'https://empathy.co',
position: 1,
image: '/img/test-image-1.jpeg'
})
});
// Loading
getBanner().should('exist');
getBannerImage().should('exist');

// Success
getBanner().should('exist');
getBannerImage().should('exist');
});

it('banner is not rendered if image load fails', () => {
const { getBanner, getBannerImage } = mountBanner({
banner: createBannerStub('banner', {
title: 'Search UIs',
url: 'https://empathy.co',
position: 1
})
});
// Loading
getBanner().should('exist');
getBannerImage().should('exist');

// Error
getBanner().should('not.exist');
});
});

interface MountBannerOptions {
/** The banner data. */
banner?: BannerModel;
/** The template to be rendered. */
template?: string;
}

interface MountBannerAPI {
getBanner: () => Cypress.Chainable<JQuery>;
getBannerImage: () => Cypress.Chainable<JQuery>;
}
4 changes: 2 additions & 2 deletions packages/x-types/src/banner.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import { Taggable } from './tagging.model';
*/
export interface Banner extends NamedModel<'Banner'>, Identifiable, Taggable {
/** Banner title. */
title: string;
title?: string;
/** URL to redirect. */
url: string;
url?: string;
/** Banner image. */
image: string;
/** Banner position (= row) inside the grid. */
Expand Down