-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create leaderboard info component (#606)
* Use type imports in lib/api * Add compoments, elements, and most of the styles * Update vitest recommended extension * REBASE: Add components * Swap component use and add styles * Allow styles to be passed to DropDown * Rename LeaderboardDetails to LeaderboardInfo * Create separate components for mobile and desktop views * Miscellaneous style and attribute tweaks * Add missing icons And some substitutes, really * Rename components * Use icons in components * Move socials array into Desktop component * Fix component styling Sorry this is a huge commit * Add event handling for DropDownItem * Rework component tree * Apply roundabout way to get dropdown borders to work * Add tests * Remove unnecessary state check in DropDownItem * Change class name * Rename files and fix first Item padding * Fix... lint errors? * Uh * Replace prop with event emit * Added tests in components I forgot about lol * Rename files to new casing * Address comments * Make component reactive to window resizing --------- Co-authored-by: Edward Runkel <[email protected]>
- Loading branch information
Showing
38 changed files
with
2,898 additions
and
1,585 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { enableAutoUnmount, mount } from '@vue/test-utils' | ||
import { vi } from 'vitest' | ||
import { getByTestId } from 'root/testUtils' | ||
import Desktop from './Desktop.vue' | ||
|
||
describe('<Desktop />', () => { | ||
function getDesktopWrapper() { | ||
return mount(Desktop, { | ||
props: { | ||
leaderboard: { | ||
categories: [{ id: 1, name: 'Any%', slug: 'any' }], | ||
id: 1, | ||
name: 'Stuck in the Train Simulator 2', | ||
slug: 'stuck-in-the-train-sim-2', | ||
}, | ||
todoPlatforms: ['XBox', 'PC'], | ||
}, | ||
}) | ||
} | ||
|
||
afterEach(() => { | ||
vi.restoreAllMocks() | ||
}) | ||
|
||
enableAutoUnmount(afterEach) | ||
|
||
it('should render without crashing', () => { | ||
const wrapper = getDesktopWrapper() | ||
expect(wrapper.isVisible()).toBe(true) | ||
|
||
expect(getByTestId(wrapper, 'title').text()).toBe( | ||
'Stuck in the Train Simulator 2', | ||
) | ||
expect(getByTestId(wrapper, 'tag').text()).toBe('Any%') | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
<script setup lang="ts"> | ||
import Dropdown from 'elements/buttons/Dropdown/Dropdown.vue' | ||
import DropdownItem from 'elements/buttons/Dropdown/DropdownItem.vue' | ||
import ButtonLink from 'elements/buttons/ButtonLink/ButtonLink.vue' | ||
import FollowButton from 'elements/buttons/FollowButton/FollowButton.vue' | ||
import SocialButtons from 'elements/buttons/SocialButtons/SocialButtons.vue' | ||
import Tag from 'elements/tags/Tag/Tag.vue' | ||
import PlatformTags from '../PlatformTags/PlatformTags.vue' | ||
import type { LeaderboardInfoProps } from '../LeaderboardInfo.vue' | ||
interface Props extends LeaderboardInfoProps { | ||
todoPlatforms: string[] | ||
} | ||
const emit = defineEmits<{ | ||
(event: 'follow'): void | ||
}>() | ||
// TODO: Get actual links tied to the leaderboard | ||
const socials: Socials[] = [ | ||
{ | ||
icon: 'twitch', | ||
name: 'Twitch', | ||
url: 'https://twitch.tv', | ||
}, | ||
{ | ||
icon: 'globe', | ||
name: 'Site', | ||
url: '#', | ||
}, | ||
{ | ||
icon: 'discord', | ||
name: 'Discord', | ||
url: 'https://discord.gg', | ||
}, | ||
{ | ||
icon: 'message', | ||
name: 'Message', | ||
url: '#', | ||
}, | ||
] | ||
defineProps<Props>() | ||
</script> | ||
|
||
<template> | ||
<div class="leaderboard-info"> | ||
<!-- TODO: Do not provide defaults and instead return nothing if on production --> | ||
<div class="title" data-testid="title"> | ||
{{ leaderboard.name || 'Game Name' }} | ||
</div> | ||
<div class="body"> | ||
<img | ||
src="https://via.placeholder.com/220x220" | ||
alt="game-art" | ||
class="body__game-art" | ||
/> | ||
<Tag data-testid="tag">{{ | ||
leaderboard.categories[0]?.name ?? 'TODO' | ||
}}</Tag> | ||
<span class="body__published-year">YEAR</span> | ||
<PlatformTags :tags="todoPlatforms" /> | ||
<FollowButton | ||
class="body__follow" | ||
data-testid="body__follow" | ||
@follow="emit('follow')" | ||
/> | ||
<div class="body__divider" /> | ||
<div class="body__internal-link-buttons"> | ||
<ButtonLink class="body__internal-link-buttons__button" to="#"> | ||
<i-svg-book /> | ||
<span>Guides</span> | ||
</ButtonLink> | ||
<ButtonLink class="body__internal-link-buttons__button" to="#"> | ||
<i-svg-link /> | ||
<div>Resources</div> | ||
</ButtonLink> | ||
<Dropdown class-name="body__internal-link-buttons__button"> | ||
<DropdownItem>First</DropdownItem> | ||
<DropdownItem>Second</DropdownItem> | ||
<DropdownItem>Third</DropdownItem> | ||
</Dropdown> | ||
</div> | ||
<SocialButtons :socials="socials" /> | ||
</div> | ||
</div> | ||
</template> | ||
|
||
<style lang="postcss" scoped> | ||
.icon { | ||
@apply w-2; | ||
} | ||
.leaderboard-info { | ||
@apply h-[fit-content] min-h-[28.125rem] w-[fit-content] relative; | ||
@apply flex flex-col items-center; | ||
@apply border-gray-300 border rounded; | ||
& .title { | ||
@apply w-full text-center; | ||
@apply font-bold text-lg p-3; | ||
@apply border-b-gray-200 border-b-2; | ||
} | ||
& .body { | ||
@apply m-4; | ||
@apply flex flex-col items-center gap-1; | ||
& .body__game-art { | ||
@apply my-1 px-8; | ||
} | ||
& .body__published-year { | ||
@apply text-sm; | ||
} | ||
& .body__follow { | ||
@apply mt-3; | ||
} | ||
& .body__divider { | ||
@apply border-b border-gray-300 w-full my-3; | ||
} | ||
& .body__internal-link-buttons { | ||
@apply w-full flex flex-col mb-4 gap-2; | ||
& .body__internal-link-buttons__button { | ||
@apply w-full flex justify-center items-center gap-2 bg-white hover:bg-gray-100; | ||
@apply border-gray-200 border-2 rounded; | ||
} | ||
& :deep(.body__internal-link-buttons__button) { | ||
@apply w-full bg-white hover:bg-gray-100; | ||
@apply border-gray-200 border-2 rounded; | ||
} | ||
} | ||
} | ||
} | ||
</style> | ||
elements/buttons/Dropdown/Dropdown.vueelements/buttons/Dropdown/DropdownItem.vue |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import { enableAutoUnmount, mount } from '@vue/test-utils' | ||
import { vi } from 'vitest' | ||
import { getByTestId } from 'root/testUtils' | ||
import LeaderboardInfo from './LeaderboardInfo.vue' | ||
|
||
describe('<LeaderboardInfo />', () => { | ||
function getLeaderboardInfoWrapper() { | ||
return mount(LeaderboardInfo, { | ||
props: { | ||
leaderboard: { | ||
categories: [], | ||
id: 1, | ||
name: 'Stuck in the Train Simulator 2', | ||
slug: 'stuck-in-the-train-sim-2', | ||
socials: [ | ||
{ | ||
icon: 'disc', | ||
name: 'discord', | ||
url: 'https://discord.gg/leaderboards.gg', | ||
}, | ||
{ | ||
icon: 'youtube', | ||
name: 'Socials', | ||
url: 'https://youtube.com', | ||
}, | ||
{ | ||
icon: 'twit', | ||
name: 'twitter', | ||
url: 'https://twitter.com/bestofdyingtwit', | ||
}, | ||
], | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
afterEach(() => { | ||
vi.restoreAllMocks() | ||
vi.unstubAllGlobals() | ||
}) | ||
|
||
enableAutoUnmount(afterEach) | ||
|
||
it('should render without crashing', () => { | ||
const wrapper = getLeaderboardInfoWrapper() | ||
expect(wrapper.isVisible()).toBe(true) | ||
}) | ||
|
||
it('should render <Desktop /> if device width is large', () => { | ||
vi.stubGlobal('innerWidth', 1980) | ||
const wrapper = getLeaderboardInfoWrapper() | ||
expect(wrapper.html()).toContain('Guides') | ||
}) | ||
|
||
it('should render <Mobile /> if device width is small', () => { | ||
vi.stubGlobal('innerWidth', 600) | ||
const wrapper = getLeaderboardInfoWrapper() | ||
expect(wrapper.html()).toContain('Submit Run') | ||
}) | ||
|
||
// TODO: The follow event doesn't trigger in the test, somehow | ||
it.skip('should emit event when the Follow Button is triggered', async () => { | ||
const wrapper = getLeaderboardInfoWrapper() | ||
await getByTestId(wrapper, 'child').trigger('follow') | ||
expect(wrapper.emitted().follow).toBeTruthy() | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
<script setup lang="ts"> | ||
import { useThrottleFn } from '@vueuse/core' | ||
import resolveConfig from 'tailwindcss/resolveConfig' | ||
import { ref } from 'vue' | ||
import tailwindConfig from 'root/tailwind.config' | ||
import Desktop from './Desktop/Desktop.vue' | ||
import Mobile from './Mobile/Mobile.vue' | ||
import type { LeaderboardViewModel } from 'lib/api/data-contracts' | ||
export interface LeaderboardInfoProps { | ||
leaderboard: LeaderboardViewModel | ||
} | ||
// TODO: Remove this. Get from model instead. | ||
const todoPlatforms = ['PS4', 'PC', 'XboxSeriesX'] | ||
// TODO: Implement listeners | ||
const emit = defineEmits<{ | ||
(event: 'follow', leaderboardId: number): void | ||
}>() | ||
const mobileWidth = parseInt( | ||
resolveConfig(tailwindConfig).theme.screens.sm.replace('px', ''), | ||
10, | ||
) | ||
const isMobile = ref(window.innerWidth <= mobileWidth) | ||
function checkIsMobile() { | ||
isMobile.value = window.innerWidth <= mobileWidth | ||
} | ||
window.addEventListener('resize', useThrottleFn(checkIsMobile, 20)) | ||
defineProps<LeaderboardInfoProps>() | ||
</script> | ||
|
||
<template> | ||
<Mobile | ||
v-if="isMobile.valueOf()" | ||
data-testid="child" | ||
:leaderboard="leaderboard" | ||
:todo-platforms="todoPlatforms" | ||
@follow="emit('follow', leaderboard.id)" | ||
/> | ||
<Desktop | ||
v-else | ||
data-testid="child" | ||
:leaderboard="leaderboard" | ||
:todo-platforms="todoPlatforms" | ||
@follow="emit('follow', leaderboard.id)" | ||
/> | ||
</template> | ||
|
||
<style lang="postcss" scoped></style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { enableAutoUnmount, mount } from '@vue/test-utils' | ||
import { vi } from 'vitest' | ||
import { getByTestId } from 'root/testUtils' | ||
import Mobile from './Mobile.vue' | ||
|
||
describe('<Mobile />', () => { | ||
function getMobileWrapper() { | ||
return mount(Mobile, { | ||
props: { | ||
leaderboard: { | ||
categories: [{ id: 1, name: 'Any%', slug: 'any' }], | ||
id: 1, | ||
name: 'Stuck in the Train Simulator 2', | ||
slug: 'stuck-in-the-train-sim-2', | ||
}, | ||
todoPlatforms: ['XBox', 'PC'], | ||
}, | ||
}) | ||
} | ||
|
||
afterEach(() => { | ||
vi.restoreAllMocks() | ||
}) | ||
|
||
enableAutoUnmount(afterEach) | ||
|
||
it('should render without crashing', () => { | ||
const wrapper = getMobileWrapper() | ||
expect(wrapper.isVisible()).toBe(true) | ||
|
||
expect(getByTestId(wrapper, 'title').text()).toBe( | ||
'Stuck in the Train Simulator 2', | ||
) | ||
expect(getByTestId(wrapper, 'tag').text()).toBe('Any%') | ||
}) | ||
}) |
Oops, something went wrong.