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

Create leaderboard info component ii #607

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
"bradlc.vscode-tailwindcss", // Tooling for Tailwind
"csstools.postcss", // PostCSS as Tailwind is built on top of it
"Vue.volar", // Vue 3 Language support for VSC https://github.com/johnsoncodehk/volar
"ZixuanChen.vitest-explorer" // Vitest extension to run tests inside of VSCode
"vitest.explorer" // Vitest extension to run tests inside of VSCode
]
}
21 changes: 21 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "firefox",
"request": "launch",
"name": "Vue",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}",
"pathMappings": [
{
"url": "webpack:///src/",
"path": "${webRoot}/"
}
]
}
]
}
4 changes: 4 additions & 0 deletions assets/sprite/svg/book.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions assets/sprite/svg/link.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ export {}

declare module 'vue' {
export interface GlobalComponents {
ISvgBook: typeof import('~icons/svg/book')['default']
ISvgCircleCheck: typeof import('~icons/svg/circle-check')['default']
ISvgCircleExclamation: typeof import('~icons/svg/circle-exclamation')['default']
ISvgCircleInfo: typeof import('~icons/svg/circle-info')['default']
ISvgClock: typeof import('~icons/svg/clock')['default']
ISvgClose: typeof import('~icons/svg/close')['default']
ISvgEyeHidden: typeof import('~icons/svg/eye-hidden')['default']
ISvgEyeVisible: typeof import('~icons/svg/eye-visible')['default']
ISvgLink: typeof import('~icons/svg/link')['default']
ISvgMenu: typeof import('~icons/svg/menu')['default']
ISvgSearch: typeof import('~icons/svg/search')['default']
ISvgSpinner: typeof import('~icons/svg/spinner')['default']
Expand Down
36 changes: 36 additions & 0 deletions components/blocks/LeaderboardInfo/Desktop/Desktop.test.ts
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%')
})
})
141 changes: 141 additions & 0 deletions components/blocks/LeaderboardInfo/Desktop/Desktop.vue
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
67 changes: 67 additions & 0 deletions components/blocks/LeaderboardInfo/LeaderboardInfo.test.ts
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()
})
})
44 changes: 44 additions & 0 deletions components/blocks/LeaderboardInfo/LeaderboardInfo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<script setup lang="ts">
import resolveConfig from 'tailwindcss/resolveConfig'
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 isMobile =
window.innerWidth <=
parseInt(resolveConfig(tailwindConfig).theme.screens.sm.replace('px', ''), 10)

defineProps<LeaderboardInfoProps>()
</script>

<template>
<Desktop
v-if="!isMobile"
data-testid="child"
:leaderboard="leaderboard"
:todo-platforms="todoPlatforms"
@follow="emit('follow', leaderboard.id)"
/>
<Mobile
v-if="isMobile"
data-testid="child"
:leaderboard="leaderboard"
:todo-platforms="todoPlatforms"
@follow="emit('follow', leaderboard.id)"
/>
</template>

<style lang="postcss" scoped></style>
36 changes: 36 additions & 0 deletions components/blocks/LeaderboardInfo/Mobile/Mobile.test.ts
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%')
})
})
Loading
Loading