Skip to content
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
14 changes: 7 additions & 7 deletions src/partials/HeaderPartial.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
rel="noopener noreferrer"
:title="link.title"
>
<svg class="h-4 w-4" viewBox="0 0 16 16" aria-hidden="true">
<svg class="h-4 w-4" viewBox="0 0 24 24" aria-hidden="true">
<path :class="link.iconClass" :d="link.icon" />
</svg>
<span class="sr-only">{{ link.label }}</span>
Expand All @@ -47,21 +47,21 @@
<div class="flex items-center">
<input id="light-switch" type="checkbox" name="light-switch" class="light-switch sr-only" @click="toggleDarkMode" />
<label class="relative inline-flex h-8 w-8 cursor-pointer items-center justify-center rounded-md p-2 transition-colors" for="light-switch">
<svg class="dark:hidden" width="16" height="16" xmlns="http://www.w3.org/2000/svg">
<svg class="dark:hidden h-4 w-4" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path
class="fill-fuchsia-300 hover:fill-fuchsia-600"
d="M7 0h2v2H7zM12.88 1.637l1.414 1.415-1.415 1.413-1.413-1.414zM14 7h2v2h-2zM12.95 14.433l-1.414-1.413 1.413-1.415 1.415 1.414zM7 14h2v2H7zM2.98 14.364l-1.413-1.415 1.414-1.414 1.414 1.415zM0 7h2v2H0zM3.05 1.706 4.463 3.12 3.05 4.535 1.636 3.12z"
d="M10.5 0h3v3h-3zM19.32 2.455l2.121 2.122-2.122 2.119-2.119-2.121zM21 10.5h3v3h-3zM19.425 21.649l-2.121-2.119 2.119-2.123 2.123 2.121zM10.5 21h3v3h-3zM4.47 21.546l-2.119-2.123 2.121-2.121 2.121 2.123zM0 10.5h3v3H0zM4.575 2.559 6.695 4.68 4.575 6.803 2.454 4.68z"
/>
<path class="fill-fuchsia-400 hover:fill-fuchsia-600" d="M8 4C5.8 4 4 5.8 4 8s1.8 4 4 4 4-1.8 4-4-1.8-4-4-4Z" />
<path class="fill-fuchsia-400 hover:fill-fuchsia-600" d="M12 6c-3.3 0-6 2.7-6 6s2.7 6 6 6 6-2.7 6-6-2.7-6-6-6Z" />
</svg>
<svg class="hidden dark:block" width="16" height="16" xmlns="http://www.w3.org/2000/svg">
<svg class="hidden h-4 w-4 dark:block" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path
class="fill-fuchsia-400 hover:fill-fuchsia-200 dark:fill-teal-500 dark:hover:fill-teal-300"
d="M6.2 1C3.2 1.8 1 4.6 1 7.9 1 11.8 4.2 15 8.1 15c3.3 0 6-2.2 6.9-5.2C9.7 11.2 4.8 6.3 6.2 1Z"
d="M9.3 1.5C4.8 2.7 1.5 6.9 1.5 11.85 1.5 17.7 6.3 22.5 12.15 22.5c4.95 0 9-3.3 10.35-7.8C14.55 16.8 7.2 9.45 9.3 1.5Z"
/>
<path
class="fill-fuchsia-500 hover:fill-fuchsia-200 dark:fill-teal-500 dark:hover:fill-teal-300"
d="M12.5 5a.625.625 0 0 1-.625-.625 1.252 1.252 0 0 0-1.25-1.25.625.625 0 1 1 0-1.25 1.252 1.252 0 0 0 1.25-1.25.625.625 0 1 1 1.25 0c.001.69.56 1.249 1.25 1.25a.625.625 0 1 1 0 1.25c-.69.001-1.249.56-1.25 1.25A.625.625 0 0 1 12.5 5Z"
d="M18.75 7.5a.9375.9375 0 0 1-.9375-.9375 1.878 1.878 0 0 0-1.875-1.875.9375.9375 0 1 1 0-1.875 1.878 1.878 0 0 0 1.875-1.875.9375.9375 0 1 1 1.875 0c.001 1.035.84 1.873 1.875 1.875a.9375.9375 0 1 1 0 1.875c-1.035.001-1.873.84-1.875 1.875a.9375.9375 0 0 1-.9375.9375Z"
/>
</svg>
<span class="sr-only">Switch to light / dark version</span>
Expand Down
29 changes: 1 addition & 28 deletions src/partials/WidgetSocialPartial.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,41 +26,14 @@

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { socialPlatforms } from '@/support/social.ts';
import { useApiStore } from '@api/store.ts';
import { debugError } from '@api/http-error.ts';
import type { SocialResponse } from '@api/response/index.ts';

const apiStore = useApiStore();
const social = ref<SocialResponse[]>([]);

interface PlatformConfig {
icon: string;
text: string;
}

const socialPlatforms: Record<string, PlatformConfig> = {
x: {
icon: 'M13.3174 10.7749L19.1457 4H17.7646L12.7039 9.88256L8.66193 4H4L10.1122 12.8955L4 20H5.38119L10.7254 13.7878L14.994 20H19.656L13.3171 10.7749H13.3174ZM11.4257 12.9738L10.8064 12.0881L5.87886 5.03974H8.00029L11.9769 10.728L12.5962 11.6137L17.7652 19.0075H15.6438L11.4257 12.9742V12.9738Z',
text: 'Latest Updates',
},
youtube: {
icon: 'M21.582,6.186c-0.23-0.86-0.908-1.538-1.768-1.768C18.254,4,12,4,12,4S5.746,4,4.186,4.418 c-0.86,0.23-1.538,0.908-1.768,1.768C2,7.746,2,12,2,12s0,4.254,0.418,5.814c0.23,0.86,0.908,1.538,1.768,1.768 C5.746,20,12,20,12,20s6.254,0,7.814-0.418c0.861-0.23,1.538-0.908,1.768-1.768C22,16.254,22,12,22,12S22,7.746,21.582,6.186z M10,15.464V8.536L16,12L10,15.464z',
text: 'My Videos',
},
instagram: {
icon: 'M7.5,2h9A5.5,5.5,0,0,1,22,7.5v9A5.5,5.5,0,0,1,16.5,22h-9A5.5,5.5,0,0,1,2,16.5v-9A5.5,5.5,0,0,1,7.5,2ZM12,16A4,4,0,1,0,12,8,4,4,0,0,0,12,16Z',
text: 'Photo Stream',
},
linkedin: {
icon: 'M19,3H5C3.89,3,3,3.89,3,5V19C3,20.1,3.89,21,5,21H19C20.1,21,21,20.1,21,19V5C21,3.89,20.1,3,19,3M8.5,18H5.5V10H8.5V18M6.94,8.5C6.16,8.5,5.5,7.83,5.5,7C5.5,6.17,6.16,5.5,6.94,5.5C7.72,5.5,8.38,6.17,8.38,7C8.38,7.83,7.72,8.5,6.94,8.5M18.5,18H15.5V14.25C15.5,13.17,14.67,12.25,13.5,12.25C12.5,12.25,11.5,13,11.5,14.25V18H8.5V10H11.5V11.25C12.06,10.25,13,9.5,14.25,9.5C16.5,9.5,18.5,11.25,18.5,14V18Z',
text: 'Professional Profile',
},
github: {
icon: 'M12,2A10,10 0 0,0 2,12C2,16.42 4.87,20.17 8.84,21.5C9.34,21.58 9.5,21.27 9.5,21C9.5,20.77 9.5,20.14 9.5,19.31C6.73,19.91 6.14,17.97 6.14,17.97C5.68,16.81 4.97,16.5 4.97,16.5C4.05,15.82 5.06,15.82 5.06,15.82C6.06,15.89 6.63,16.83 6.63,16.83C7.5,18.31 8.95,17.88 9.5,17.61C9.58,17.03 9.84,16.6 10.12,16.34C7.89,16.1 5.5,15.27 5.5,11.5C5.5,10.39 5.89,9.53 6.5,8.84C6.38,8.58 6.08,7.7 6.63,6.5C6.63,6.5 7.43,6.26 9.5,7.7C10.27,7.5 11.14,7.39 12,7.39C12.86,7.39 13.73,7.5 14.5,7.7C16.57,6.26 17.37,6.5 17.37,6.5C17.92,7.7 17.62,8.58 17.5,8.84C18.11,9.53 18.5,10.39 18.5,11.5C18.5,15.27 16.1,16.1 13.88,16.34C14.24,16.64 14.5,17.27 14.5,18.26C14.5,19.6 14.5,20.68 14.5,21C14.5,21.27 14.66,21.59 15.17,21.5C19.14,20.16 22,16.42 22,12A10,10 0 0,0 12,2Z',
text: 'Code & Projects',
},
};

onMounted(async () => {
try {
const socialResponse = await apiStore.getSocial();
Expand Down
32 changes: 30 additions & 2 deletions src/support/social.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,34 @@ import { computed } from 'vue';
import type { Ref } from 'vue';
import type { SocialResponse } from '@api/response/index.ts';

export interface SocialPlatformConfig {
icon: string;
text: string;
}

export const socialPlatforms: Record<string, SocialPlatformConfig> = {
x: {
icon: 'M13.3174 10.7749L19.1457 4H17.7646L12.7039 9.88256L8.66193 4H4L10.1122 12.8955L4 20H5.38119L10.7254 13.7878L14.994 20H19.656L13.3171 10.7749H13.3174ZM11.4257 12.9738L10.8064 12.0881L5.87886 5.03974H8.00029L11.9769 10.728L12.5962 11.6137L17.7652 19.0075H15.6438L11.4257 12.9742V12.9738Z',
text: 'Latest Updates',
},
youtube: {
icon: 'M21.582,6.186c-0.23-0.86-0.908-1.538-1.768-1.768C18.254,4,12,4,12,4S5.746,4,4.186,4.418 c-0.86,0.23-1.538,0.908-1.768,1.768C2,7.746,2,12,2,12s0,4.254,0.418,5.814c0.23,0.86,0.908,1.538,1.768,1.768 C5.746,20,12,20,12,20s6.254,0,7.814-0.418c0.861-0.23,1.538-0.908,1.768-1.768C22,16.254,22,12,22,12S22,7.746,21.582,6.186z M10,15.464V8.536L16,12L10,15.464z',
text: 'My Videos',
},
instagram: {
icon: 'M7.5,2h9A5.5,5.5,0,0,1,22,7.5v9A5.5,5.5,0,0,1,16.5,22h-9A5.5,5.5,0,0,1,2,16.5v-9A5.5,5.5,0,0,1,7.5,2ZM12,16A4,4,0,1,0,12,8,4,4,0,0,0,12,16Z',
text: 'Photo Stream',
},
linkedin: {
icon: 'M19,3H5C3.89,3,3,3.89,3,5V19C3,20.1,3.89,21,5,21H19C20.1,21,21,20.1,21,19V5C21,3.89,20.1,3,19,3M8.5,18H5.5V10H8.5V18M6.94,8.5C6.16,8.5,5.5,7.83,5.5,7C5.5,6.17,6.16,5.5,6.94,5.5C7.72,5.5,8.38,6.17,8.38,7C8.38,7.83,7.72,8.5,6.94,8.5M18.5,18H15.5V14.25C15.5,13.17,14.67,12.25,13.5,12.25C12.5,12.25,11.5,13,11.5,14.25V18H8.5V10H11.5V11.25C12.06,10.25,13,9.5,14.25,9.5C16.5,9.5,18.5,11.25,18.5,14V18Z',
text: 'Professional Profile',
},
github: {
icon: 'M12,2A10,10 0 0,0 2,12C2,16.42 4.87,20.17 8.84,21.5C9.34,21.58 9.5,21.27 9.5,21C9.5,20.77 9.5,20.14 9.5,19.31C6.73,19.91 6.14,17.97 6.14,17.97C5.68,16.81 4.97,16.5 4.97,16.5C4.05,15.82 5.06,15.82 5.06,15.82C6.06,15.89 6.63,16.83 6.63,16.83C7.5,18.31 8.95,17.88 9.5,17.61C9.58,17.03 9.84,16.6 10.12,16.34C7.89,16.1 5.5,15.27 5.5,11.5C5.5,10.39 5.89,9.53 6.5,8.84C6.38,8.58 6.08,7.7 6.63,6.5C6.63,6.5 7.43,6.26 9.5,7.7C10.27,7.5 11.14,7.39 12,7.39C12.86,7.39 13.73,7.5 14.5,7.7C16.57,6.26 17.37,6.5 17.37,6.5C17.92,7.7 17.62,8.58 17.5,8.84C18.11,9.53 18.5,10.39 18.5,11.5C18.5,15.27 16.1,16.1 13.88,16.34C14.24,16.64 14.5,17.27 14.5,18.26C14.5,19.6 14.5,20.68 14.5,21C14.5,21.27 14.66,21.59 15.17,21.5C19.14,20.16 22,16.42 22,12A10,10 0 0,0 12,2Z',
text: 'Code & Projects',
},
};

interface HeaderPlatformConfig {
icon: string;
iconClass: string;
Expand All @@ -18,11 +46,11 @@ export interface HeaderSocialLink {

const headerSocialPlatforms: Record<string, HeaderPlatformConfig> = {
github: {
icon: 'M8 0a8 8 0 00-2.53 15.6c.4.07.55-.17.55-.38v-1.33c-2.24.49-2.71-1.08-2.71-1.08-.36-.92-.88-1.16-.88-1.16-.72-.5.05-.49.05-.49.8.06 1.22.83 1.22.83.71 1.22 1.86.87 2.31.67.07-.52.28-.87.5-1.07-1.79-.2-3.68-.9-3.68-4a3.13 3.13 0 01-.83-2.16 2.92 2.92 0 01.08-2.13s.67-.22 2.2.82a7.65 7.65 0 012 0c1.53-1 2.2-.82 2.2-.82a2.92 2.92 0 01.08 2.13 3.13 3.13 0 01.83 2.16c0 3.11-1.9 3.8-3.7 4 .29.25.54.74.54 1.5v2.22c0 .21.15.45.56.38A8 8 0 008 0z',
icon: socialPlatforms.github.icon,
iconClass: 'fill-current text-slate-600 transition-colors hover:text-fuchsia-600 dark:text-teal-200 dark:hover:text-teal-300',
},
linkedin: {
icon: 'M14.23 0H1.77A1.77 1.77 0 000 1.77v12.46A1.77 1.77 0 001.77 16h12.46A1.77 1.77 0 0016 14.23V1.77A1.77 1.77 0 0014.23 0zM4.95 13.33H2.67V6h2.28zM3.81 4.94a1.32 1.32 0 111.32-1.32 1.32 1.32 0 01-1.32 1.32zM13.33 13.33h-2.28v-3.9c0-.93-.02-2.13-1.3-2.13s-1.5 1.02-1.5 2.07v3.96H6a.05.05 0 01-.05-.05V6h2.19v1.01h.03a2.42 2.42 0 012.16-1.19c2.31 0 2.74 1.52 2.74 3.49z',
icon: socialPlatforms.linkedin.icon,
iconClass: 'fill-current text-slate-600 transition-colors hover:text-fuchsia-600 dark:text-teal-200 dark:hover:text-teal-300',
},
};
Expand Down
39 changes: 38 additions & 1 deletion tests/partials/HeaderPartial.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { mount } from '@vue/test-utils';
import { ref } from 'vue';
import { faker } from '@faker-js/faker';
import { describe, it, expect, vi } from 'vitest';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import HeaderPartial from '@partials/HeaderPartial.vue';
import { useHeaderSocialLinks } from '@/support/social.ts';

Expand Down Expand Up @@ -33,6 +33,12 @@ vi.mock('@api/store.ts', () => ({ useApiStore: () => ({ setSearchTerm, getSocial
vi.mock('@api/http-error.ts', () => ({ debugError: vi.fn() }));

describe('HeaderPartial', () => {
beforeEach(() => {
setSearchTerm.mockClear();
getSocial.mockClear();
toggleDarkMode.mockClear();
});

it('validates search length', async () => {
const wrapper = mount(HeaderPartial);
const input = wrapper.find('#search');
Expand Down Expand Up @@ -78,5 +84,36 @@ describe('HeaderPartial', () => {
expect(links[1].find('span.sr-only').text()).toBe(linkedinLink?.description);
expect(links[0].find('path').attributes('d')).toBe(helperLinks[0]?.icon);
expect(links[1].find('path').attributes('d')).toBe(helperLinks[1]?.icon);

expect(links.map((link) => link.find('svg').attributes('viewBox'))).toEqual(['0 0 24 24', '0 0 24 24']);

const themeIcons = wrapper.findAll('label[for="light-switch"] svg');
expect(themeIcons).toHaveLength(2);
themeIcons.forEach((icon) => {
expect(icon.attributes('viewBox')).toBe('0 0 24 24');
});
});

it('clears invalid searches and resets the store term', async () => {
const wrapper = mount(HeaderPartial);
const input = wrapper.find('#search');

await input.setValue('abcd');
await wrapper.find('form').trigger('submit');
await wrapper.vm.$nextTick();

expect(wrapper.vm.validationError).toBeDefined();
expect(setSearchTerm).not.toHaveBeenCalled();

const clearButton = wrapper.find('svg[title="Clear search"]');
expect(clearButton.exists()).toBe(true);

await clearButton.trigger('click');
await wrapper.vm.$nextTick();
await Promise.resolve();

expect(wrapper.vm.validationError).toBe('');
expect(wrapper.vm.searchQuery).toBe('');
expect(setSearchTerm).toHaveBeenCalledWith('');
});
});
100 changes: 100 additions & 0 deletions tests/support/header-social-links.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { ref } from 'vue';
import { describe, it, expect } from 'vitest';
import type { SocialResponse } from '@api/response/index.ts';
import { socialPlatforms, useHeaderSocialLinks } from '@/support/social.ts';

describe('useHeaderSocialLinks', () => {
it('builds ordered header links with trimmed metadata', () => {
const social = ref<SocialResponse[]>([
{
uuid: 'github-id',
name: 'github',
url: 'https://github.example.com',
handle: ' ignored ',
description: ' Showcase ',
},
{
uuid: 'linkedin-id',
name: 'linkedin',
url: 'https://linkedin.example.com',
handle: ' @example ',
description: ' ',
},
{
uuid: 'other-id',
name: 'mastodon',
url: 'https://social.example.com',
handle: '@other',
description: 'Other network',
},
]);

const links = useHeaderSocialLinks(social).value;

expect(links).toHaveLength(2);
expect(links[0]).toMatchObject({
name: 'github',
url: 'https://github.example.com',
label: 'Showcase',
title: 'Showcase',
icon: socialPlatforms.github.icon,
iconClass: expect.stringContaining('text-slate-600'),
});
expect(links[1]).toMatchObject({
name: 'linkedin',
url: 'https://linkedin.example.com',
label: '@example',
title: '@example',
icon: socialPlatforms.linkedin.icon,
});
});

it('reacts to social feed updates and falls back to platform name', () => {
const social = ref<SocialResponse[]>([]);
const headerLinks = useHeaderSocialLinks(social);

expect(headerLinks.value).toEqual([]);

social.value = [
{
uuid: 'linkedin-id',
name: 'linkedin',
url: 'https://linkedin.example.com',
handle: '',
description: '',
},
];

expect(headerLinks.value).toMatchObject([
{
name: 'linkedin',
url: 'https://linkedin.example.com',
label: 'linkedin',
title: 'linkedin',
icon: socialPlatforms.linkedin.icon,
iconClass: expect.stringContaining('text-slate-600'),
},
]);

social.value = [
{
uuid: 'github-id',
name: 'github',
url: 'https://github.example.com',
handle: '',
description: ' ',
},
];

expect(headerLinks.value).toMatchObject([
{
name: 'github',
url: 'https://github.example.com',
label: 'github',
title: 'github',
icon: socialPlatforms.github.icon,
iconClass: expect.stringContaining('text-slate-600'),
},
]);
});
});
Loading