diff --git a/.storybook/stories/TheHeader.stories.js b/.storybook/stories/TheHeader.stories.js index 49facb5a41..f5ae40b156 100644 --- a/.storybook/stories/TheHeader.stories.js +++ b/.storybook/stories/TheHeader.stories.js @@ -23,6 +23,7 @@ const loggedIn = { "https://www-dev-kiva-org.freetls.fastly.net/img/s140/726677.jpg", }, }, + team: null, isBorrower: false, mostRecentBorrowedLoan: null, trustee: null, @@ -66,6 +67,84 @@ const loggedInLargeCart = { } }; +const loggedInWithOneTeam = { + my: { + ...loggedIn.my, + teams: { + totalCount: 1, + values: [ + { + id: 1, + team: { + id: 1, + name: 'Team 1', + teamPublicId: 'team1', + } + }, + ], + }, + }, +}; + +const loggedInWithMultipleTeams = { + my: { + ...loggedIn.my, + teams: { + totalCount: 6, + values: [ + { + id: 1, + team: { + id: 1, + name: '(A+) Atheists, Agnostics, Skeptics, Freethinkers, Secular Humanists and the Non-Religious', + teamPublicId: 'aplus', + } + }, + { + id: 2, + team: { + id: 2, + name: 'Team 2', + teamPublicId: 'team2', + } + }, + { + id: 3, + team: { + id: 3, + name: 'Team 3', + teamPublicId: 'team3', + } + }, + { + id: 4, + team: { + id: 4, + name: 'Team 4', + teamPublicId: 'team4', + } + }, + { + id: 5, + team: { + id: 5, + name: 'Team 5', + teamPublicId: 'team5', + } + }, + { + id: 6, + team: { + id: 6, + name: 'Team 6', + teamPublicId: 'team6', + } + }, + ], + }, + }, +}; + const provideMockedApollo = (mockedResult) => { return { readQuery() { @@ -223,4 +302,32 @@ Minimal.args = { minimal: true, }; +export const LoggedInWithOneTeam = (args, { argTypes }) => ({ + props: Object.keys(argTypes), + components: { + TheHeader, + }, + mixins: [cookieStoreStoryMixin(), kvAuth0StoryMixin], + provide: { + apollo: provideMockedApollo(loggedInWithOneTeam), + }, + template: ` + + `, +}); + +export const LoggedInWithMultipleTeams = (args, { argTypes }) => ({ + props: Object.keys(argTypes), + components: { + TheHeader, + }, + mixins: [cookieStoreStoryMixin(), kvAuth0StoryMixin], + provide: { + apollo: provideMockedApollo(loggedInWithMultipleTeams), + }, + template: ` + + `, +}); + // TODO: trustee diff --git a/src/components/WwwFrame/Header/TeamsMenu.vue b/src/components/WwwFrame/Header/TeamsMenu.vue new file mode 100644 index 0000000000..24978160e2 --- /dev/null +++ b/src/components/WwwFrame/Header/TeamsMenu.vue @@ -0,0 +1,119 @@ + + + + + diff --git a/src/components/WwwFrame/TheHeader.vue b/src/components/WwwFrame/TheHeader.vue index 8619340538..07f0f2b79f 100644 --- a/src/components/WwwFrame/TheHeader.vue +++ b/src/components/WwwFrame/TheHeader.vue @@ -217,6 +217,13 @@ Borrow + + +
import('@/components/WwwFrame/LendMenu/TheLendMenu'), + TeamsMenu, }, inject: ['apollo', 'cookieStore', 'kvAuth0'], data() { @@ -633,6 +642,7 @@ export default { hasEverLoggedIn: false, isMobile: false, basketTotal: 0, + teams: null, }; }, props: { @@ -749,6 +759,7 @@ export default { this.basketTotal = data.shop?.basket?.items?.values?.reduce((sum, item) => { return sum + +(item?.price ?? 0); }, 0) ?? 0; + this.teams = data?.my?.teams ?? {}; }, errorHandlers: { 'shop.invalidBasketId': ({ cookieStore, route }) => { diff --git a/src/graphql/query/wwwHeader.graphql b/src/graphql/query/wwwHeader.graphql index 9bd9aee72a..1f348e25a5 100644 --- a/src/graphql/query/wwwHeader.graphql +++ b/src/graphql/query/wwwHeader.graphql @@ -54,5 +54,16 @@ query wwwHeader($basketId: String) { trustee { id } + teams (limit: 5) { + totalCount + values { + id + team { + id + name + teamPublicId + } + } + } } } diff --git a/test/unit/specs/components/WwwFrame/TeamsMenu.spec.js b/test/unit/specs/components/WwwFrame/TeamsMenu.spec.js new file mode 100644 index 0000000000..9a787d6ca3 --- /dev/null +++ b/test/unit/specs/components/WwwFrame/TeamsMenu.spec.js @@ -0,0 +1,170 @@ +import { render } from '@testing-library/vue'; +import TeamsMenu from '@/components/WwwFrame/Header/TeamsMenu'; +import Vue from 'vue'; +import CookieStore from '@/util/cookieStore'; +import { MockKvAuth0 } from '@/util/KvAuth0'; + +const user = { + my: { + teams: { + totalCount: 0, + values: [], + }, + }, +}; + +const userWithOneTeam = { + my: { + teams: { + totalCount: 1, + values: [ + { + id: 1, + team: { + id: 1, + name: 'Team 1', + teamPublicId: 'team1', + }, + }, + ], + }, + }, +}; + +const userWithMultipleTeams = { + my: { + teams: { + totalCount: 6, + values: [ + { + id: 1, + team: { + id: 1, + name: 'Team 1', + teamPublicId: 'team1', + }, + }, + { + id: 2, + team: { + id: 2, + name: 'Team 2', + teamPublicId: 'team2', + }, + }, + { + id: 3, + team: { + id: 3, + name: 'Team 3', + teamPublicId: 'team3', + }, + }, + { + id: 4, + team: { + id: 4, + name: 'Team 4', + teamPublicId: 'team4', + }, + }, + { + id: 5, + team: { + id: 5, + name: 'Team 5', + teamPublicId: 'team5', + }, + }, + { + id: 6, + team: { + id: 6, + name: 'Team 6', + teamPublicId: 'team6', + }, + }, + ], + }, + }, +}; + +function renderTeamsMenu(props) { + Vue.directive('kv-track-event', () => ({})); + + return render(TeamsMenu, { + props, + provide: { + apollo: { + readFragment: () => {}, + query: () => Promise.resolve({}), + readQuery: () => {}, + }, + cookieStore: new CookieStore(), + kvAuth0: MockKvAuth0, + }, + stubs: ['router-link'] + }); +} + +describe('TeamsMenu', () => { + it('should show only Teams link', async () => { + const props = { + teams: user.my.teams, + }; + const { queryByTestId } = renderTeamsMenu(props); + + const anchor = queryByTestId('header-teams'); + expect(anchor.getAttribute('to')).toBe('/teams'); + }); + + it('should show Teams Dropdown with one team option', async () => { + const props = { + teams: userWithOneTeam.my.teams, + }; + const { queryByText } = renderTeamsMenu(props); + + const activity = queryByText("My Team's activity"); + const impact = queryByText("My Team's impact"); + const join = queryByText('Join another team'); + + const { teamPublicId } = props.teams.values[0].team; + + expect(activity.getAttribute('href')).toBe(`/team/${teamPublicId}`); + expect(impact.getAttribute('href')).toBe(`/team/${teamPublicId}/impact`); + expect(join.getAttribute('href')).toBe('/teams'); + }); + + it('should show 3 teams', async () => { + const limitedTeams = userWithMultipleTeams.my.teams.values.slice(0, 3); + const props = { + teams: { + totalCount: limitedTeams.length, + values: limitedTeams, + }, + }; + const { queryByText } = renderTeamsMenu(props); + + props.teams.values.forEach(t => { + const { teamPublicId } = t.team; + expect(queryByText(t.team.name).getAttribute('href')).toBe(`/team/${teamPublicId}`); + }); + }); + + it('should only show 5 teams and a link to view all my teams', async () => { + const props = { + teams: userWithMultipleTeams.my.teams, + }; + const { queryByText } = renderTeamsMenu(props); + + const limitedTeams = props.teams.values.slice(0, 5); + + limitedTeams.forEach(t => { + const { teamPublicId } = t.team; + expect(queryByText(t.team.name).getAttribute('href')).toBe(`/team/${teamPublicId}`); + }); + + const allMyTeams = queryByText('View all my teams'); + expect(allMyTeams.getAttribute('href')).toBe('/teams/my-teams'); + }); +});