-
Notifications
You must be signed in to change notification settings - Fork 0
[feature] Add Unit Tests for: partials and more #15
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
Merged
Merged
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
8abda0e
Add unit tests for partial components
gocanto 30c9904
Use faker in partials tests
gocanto d6539e4
Refine partial tests to avoid any
gocanto 0128019
Add explicit typings to partial component tests
gocanto 254aabd
Refine partial component tests
gocanto 0c9e12d
Align partial test mocks with API types
gocanto e2e9583
Improve partial tests
gocanto f894bd4
Add jsdom dev dependency
gocanto 9f943b9
add vue test utils
gocanto 1659742
Configure vitest to process Vue components
gocanto e46003b
fix: import vitest globals in partial tests
gocanto 5fb0bf9
Fix talks partial test to match rendered content
gocanto dd7f57c
Add page component tests
gocanto e20b947
test: avoid any usage in api store tests
gocanto b214e7b
Expand page tests and enhance partial assertions
gocanto 5e0ff58
Fix failing partial tests and workflow
gocanto 65194e1
Improve partial tests
gocanto bed1d9c
Use deterministic data in talks test
gocanto 2bbde13
Fix TalksPartial test
gocanto File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or 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 hidden or 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,69 @@ | ||
| import { mount, flushPromises } from '@vue/test-utils'; | ||
| import { faker } from '@faker-js/faker'; | ||
| import { describe, it, expect, vi } from 'vitest'; | ||
| import AboutPage from '@pages/AboutPage.vue'; | ||
| import type { ProfileResponse, ProfileSkillResponse } from '@api/response/index.ts'; | ||
|
|
||
| const skills: ProfileSkillResponse[] = [ | ||
| { | ||
| uuid: faker.string.uuid(), | ||
| percentage: faker.number.int({ min: 1, max: 100 }), | ||
| item: faker.lorem.word(), | ||
| description: faker.lorem.sentence(), | ||
| }, | ||
| ]; | ||
|
|
||
| const profile: ProfileResponse = { | ||
| nickname: faker.word.words(1).toLowerCase(), | ||
| handle: faker.internet.userName(), | ||
| name: faker.person.fullName(), | ||
| email: faker.internet.email(), | ||
| profession: faker.person.jobTitle(), | ||
| skills, | ||
| }; | ||
|
|
||
| const getProfile = vi.fn<[], Promise<{ data: ProfileResponse }>>(() => | ||
| Promise.resolve({ data: profile }) | ||
| ); | ||
|
|
||
| vi.mock('@api/store.ts', () => ({ useApiStore: () => ({ getProfile }) })); | ||
| vi.mock('@api/http-error.ts', () => ({ debugError: vi.fn() })); | ||
|
|
||
| describe('AboutPage', () => { | ||
| it('shows formatted nickname', async () => { | ||
| const wrapper = mount(AboutPage, { | ||
| global: { | ||
| stubs: { | ||
| SideNavPartial: true, | ||
| HeaderPartial: true, | ||
| WidgetSocialPartial: true, | ||
| WidgetSkillsPartial: true, | ||
| FooterPartial: true, | ||
| }, | ||
| }, | ||
| }); | ||
| await flushPromises(); | ||
| const formatted = profile.nickname.charAt(0).toUpperCase() + profile.nickname.slice(1); | ||
| expect(getProfile).toHaveBeenCalled(); | ||
| expect(wrapper.find('h1').text()).toContain(formatted); | ||
| }); | ||
|
|
||
| it('handles profile errors gracefully', async () => { | ||
| const error = new Error('fail'); | ||
| getProfile.mockRejectedValueOnce(error); | ||
| const wrapper = mount(AboutPage, { | ||
| global: { | ||
| stubs: { | ||
| SideNavPartial: true, | ||
| HeaderPartial: true, | ||
| WidgetSocialPartial: true, | ||
| WidgetSkillsPartial: true, | ||
| FooterPartial: true, | ||
| }, | ||
| }, | ||
| }); | ||
| await flushPromises(); | ||
| const { debugError } = await import('@api/http-error.ts'); | ||
| expect(debugError).toHaveBeenCalledWith(error); | ||
| }); | ||
| }); | ||
This file contains hidden or 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,76 @@ | ||
| import { mount, flushPromises } from '@vue/test-utils'; | ||
| import { faker } from '@faker-js/faker'; | ||
| import { describe, it, expect, vi } from 'vitest'; | ||
| import HomePage from '@pages/HomePage.vue'; | ||
| import type { ProfileResponse, ProfileSkillResponse } from '@api/response/index.ts'; | ||
|
|
||
| const skills: ProfileSkillResponse[] = [ | ||
| { | ||
| uuid: faker.string.uuid(), | ||
| percentage: faker.number.int({ min: 1, max: 100 }), | ||
| item: faker.lorem.word(), | ||
| description: faker.lorem.sentence(), | ||
| }, | ||
| ]; | ||
|
|
||
| const profile: ProfileResponse = { | ||
| nickname: faker.person.firstName(), | ||
| handle: faker.internet.userName(), | ||
| name: faker.person.fullName(), | ||
| email: faker.internet.email(), | ||
| profession: faker.person.jobTitle(), | ||
| skills, | ||
| }; | ||
|
|
||
| const getProfile = vi.fn<[], Promise<{ data: ProfileResponse }>>(() => | ||
| Promise.resolve({ data: profile }) | ||
| ); | ||
|
|
||
| vi.mock('@api/store.ts', () => ({ useApiStore: () => ({ getProfile }) })); | ||
| vi.mock('@api/http-error.ts', () => ({ debugError: vi.fn() })); | ||
|
|
||
| describe('HomePage', () => { | ||
| it('loads profile on mount', async () => { | ||
| const wrapper = mount(HomePage, { | ||
| global: { | ||
| stubs: { | ||
| SideNavPartial: true, | ||
| HeaderPartial: true, | ||
| HeroPartial: true, | ||
| FooterPartial: true, | ||
| ArticlesListPartial: true, | ||
| FeaturedProjectsPartial: true, | ||
| TalksPartial: true, | ||
| WidgetSponsorPartial: true, | ||
| WidgetSkillsPartial: { template: '<div class="skills" />', props: ['skills'] }, | ||
| }, | ||
| }, | ||
| }); | ||
| await flushPromises(); | ||
| expect(getProfile).toHaveBeenCalledTimes(1); | ||
| expect(wrapper.find('.skills').exists()).toBe(true); | ||
| }); | ||
gocanto marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| it('handles profile load errors', async () => { | ||
| const error = new Error('oops'); | ||
| getProfile.mockRejectedValueOnce(error); | ||
| const wrapper = mount(HomePage, { | ||
| global: { | ||
| stubs: { | ||
| SideNavPartial: true, | ||
| HeaderPartial: true, | ||
| HeroPartial: true, | ||
| FooterPartial: true, | ||
| ArticlesListPartial: true, | ||
| FeaturedProjectsPartial: true, | ||
| TalksPartial: true, | ||
| WidgetSponsorPartial: true, | ||
| WidgetSkillsPartial: true, | ||
| }, | ||
| }, | ||
| }); | ||
| await flushPromises(); | ||
| const { debugError } = await import('@api/http-error.ts'); | ||
| expect(debugError).toHaveBeenCalledWith(error); | ||
| }); | ||
| }); | ||
This file contains hidden or 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,101 @@ | ||
| import { mount, flushPromises } from '@vue/test-utils'; | ||
| import { faker } from '@faker-js/faker'; | ||
| import { describe, it, expect, vi } from 'vitest'; | ||
| import { ref } from 'vue'; | ||
| import PostPage from '@pages/PostPage.vue'; | ||
| import type { PostResponse } from '@api/response/index.ts'; | ||
|
|
||
| const post: PostResponse = { | ||
| uuid: faker.string.uuid(), | ||
| slug: faker.lorem.slug(), | ||
| title: faker.lorem.words(2), | ||
| excerpt: faker.lorem.sentence(), | ||
| content: faker.lorem.paragraph(), | ||
| cover_image_url: faker.image.url(), | ||
| published_at: faker.date.past().toISOString(), | ||
| created_at: faker.date.past().toISOString(), | ||
| updated_at: faker.date.recent().toISOString(), | ||
| author: { | ||
| uuid: faker.string.uuid(), | ||
| first_name: faker.person.firstName(), | ||
| last_name: faker.person.lastName(), | ||
| username: faker.internet.userName(), | ||
| display_name: faker.person.fullName(), | ||
| bio: faker.lorem.sentence(), | ||
| picture_file_name: faker.system.fileName(), | ||
| profile_picture_url: faker.image.avatar(), | ||
| }, | ||
| categories: [], | ||
| tags: [], | ||
| }; | ||
|
|
||
| const getPost = vi.fn<[], Promise<PostResponse>>(() => Promise.resolve(post)); | ||
|
|
||
| vi.mock('@api/store.ts', () => ({ useApiStore: () => ({ getPost }) })); | ||
| vi.mock('vue-router', () => ({ useRoute: () => ({ params: { slug: post.slug } }) })); | ||
| vi.mock('marked', () => ({ marked: { use: vi.fn(), parse: vi.fn(() => '<p></p>') } })); | ||
| vi.mock('dompurify', () => ({ default: { sanitize: vi.fn((html: string) => html) } })); | ||
| vi.mock('highlight.js', () => ({ default: { highlightElement: vi.fn() } })); | ||
| vi.mock('@/dark-mode.ts', () => ({ useDarkMode: () => ({ isDark: ref(false) }) })); | ||
| vi.mock('@api/http-error.ts', () => ({ debugError: vi.fn() })); | ||
|
|
||
| describe('PostPage', () => { | ||
| it('fetches post on mount', async () => { | ||
| const wrapper = mount(PostPage, { | ||
| global: { | ||
| stubs: { | ||
| SideNavPartial: true, | ||
| HeaderPartial: true, | ||
| FooterPartial: true, | ||
| WidgetSponsorPartial: true, | ||
| WidgetSkillsPartial: true, | ||
| RouterLink: { template: '<a><slot /></a>' }, | ||
| }, | ||
| }, | ||
| }); | ||
| await flushPromises(); | ||
| expect(getPost).toHaveBeenCalledWith(post.slug); | ||
| expect(wrapper.text()).toContain(post.title); | ||
| }); | ||
gocanto marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| it('processes markdown content', async () => { | ||
| const { marked } = await import('marked'); | ||
| const DOMPurify = await import('dompurify'); | ||
| const wrapper = mount(PostPage, { | ||
| global: { | ||
| stubs: { | ||
| SideNavPartial: true, | ||
| HeaderPartial: true, | ||
| FooterPartial: true, | ||
| WidgetSponsorPartial: true, | ||
| WidgetSkillsPartial: true, | ||
| RouterLink: { template: '<a><slot /></a>' }, | ||
| }, | ||
| }, | ||
| }); | ||
| await flushPromises(); | ||
| expect(marked.parse).toHaveBeenCalledWith(post.content); | ||
| expect(DOMPurify.default.sanitize).toHaveBeenCalled(); | ||
| expect(wrapper.html()).toContain('<p></p>'); | ||
| }); | ||
|
|
||
| it('handles post errors gracefully', async () => { | ||
| const error = new Error('fail'); | ||
| getPost.mockRejectedValueOnce(error); | ||
| const wrapper = mount(PostPage, { | ||
| global: { | ||
| stubs: { | ||
| SideNavPartial: true, | ||
| HeaderPartial: true, | ||
| FooterPartial: true, | ||
| WidgetSponsorPartial: true, | ||
| WidgetSkillsPartial: true, | ||
| RouterLink: { template: '<a><slot /></a>' }, | ||
| }, | ||
| }, | ||
| }); | ||
| await flushPromises(); | ||
| const { debugError } = await import('@api/http-error.ts'); | ||
| expect(debugError).toHaveBeenCalledWith(error); | ||
| }); | ||
| }); | ||
This file contains hidden or 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,90 @@ | ||
| import { mount, flushPromises } from '@vue/test-utils'; | ||
| import { faker } from '@faker-js/faker'; | ||
| import { describe, it, expect, vi } from 'vitest'; | ||
| import ProjectsPage from '@pages/ProjectsPage.vue'; | ||
| import type { ProfileResponse, ProfileSkillResponse, ProjectsResponse } from '@api/response/index.ts'; | ||
|
|
||
| const skills: ProfileSkillResponse[] = [ | ||
| { | ||
| uuid: faker.string.uuid(), | ||
| percentage: faker.number.int({ min: 1, max: 100 }), | ||
| item: faker.lorem.word(), | ||
| description: faker.lorem.sentence(), | ||
| }, | ||
| ]; | ||
|
|
||
| const profile: ProfileResponse = { | ||
| nickname: faker.person.firstName(), | ||
| handle: faker.internet.userName(), | ||
| name: faker.person.fullName(), | ||
| email: faker.internet.email(), | ||
| profession: faker.person.jobTitle(), | ||
| skills, | ||
| }; | ||
|
|
||
| const projects: ProjectsResponse[] = [ | ||
| { | ||
| uuid: faker.string.uuid(), | ||
| title: faker.lorem.words(2), | ||
| excerpt: faker.lorem.sentence(), | ||
| url: faker.internet.url(), | ||
| language: faker.lorem.word(), | ||
| icon: faker.image.avatarGitHub(), | ||
| is_open_source: true, | ||
| created_at: faker.date.past().toISOString(), | ||
| updated_at: faker.date.recent().toISOString(), | ||
| }, | ||
| ]; | ||
|
|
||
| const getProfile = vi.fn<[], Promise<{ data: ProfileResponse }>>(() => | ||
| Promise.resolve({ data: profile }) | ||
| ); | ||
| const getProjects = vi.fn<[], Promise<{ version: string; data: ProjectsResponse[] }>>(() => | ||
| Promise.resolve({ version: '1.0.0', data: projects }) | ||
| ); | ||
|
|
||
| vi.mock('@api/store.ts', () => ({ useApiStore: () => ({ getProfile, getProjects }) })); | ||
| vi.mock('@api/http-error.ts', () => ({ debugError: vi.fn() })); | ||
|
|
||
| describe('ProjectsPage', () => { | ||
| it('loads profile and projects', async () => { | ||
| const wrapper = mount(ProjectsPage, { | ||
| global: { | ||
| stubs: { | ||
| SideNavPartial: true, | ||
| HeaderPartial: true, | ||
| WidgetSponsorPartial: true, | ||
| WidgetSkillsPartial: true, | ||
| FooterPartial: true, | ||
| ProjectCardPartial: { template: '<div class="project">{{ item.title }}</div>', props: ['item'] }, | ||
| }, | ||
| }, | ||
| }); | ||
| await flushPromises(); | ||
| expect(getProfile).toHaveBeenCalled(); | ||
| expect(getProjects).toHaveBeenCalled(); | ||
| const items = wrapper.findAll('.project'); | ||
| expect(items).toHaveLength(projects.length); | ||
| expect(wrapper.text()).toContain(projects[0].title); | ||
| }); | ||
gocanto marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| it('handles API errors', async () => { | ||
| const error = new Error('oops'); | ||
| getProfile.mockRejectedValueOnce(error); | ||
| const wrapper = mount(ProjectsPage, { | ||
| global: { | ||
| stubs: { | ||
| SideNavPartial: true, | ||
| HeaderPartial: true, | ||
| WidgetSponsorPartial: true, | ||
| WidgetSkillsPartial: true, | ||
| FooterPartial: true, | ||
| ProjectCardPartial: true, | ||
| }, | ||
| }, | ||
| }); | ||
| await flushPromises(); | ||
| const { debugError } = await import('@api/http-error.ts'); | ||
| expect(debugError).toHaveBeenCalledWith(error); | ||
| }); | ||
| }); | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.