diff --git a/tests/partials/ArticleItemSkeletonPartial.test.ts b/tests/partials/ArticleItemSkeletonPartial.test.ts new file mode 100644 index 00000000..1c650679 --- /dev/null +++ b/tests/partials/ArticleItemSkeletonPartial.test.ts @@ -0,0 +1,19 @@ +import { mount } from '@vue/test-utils'; +import { describe, it, expect } from 'vitest'; +import ArticleItemSkeletonPartial from '@partials/ArticleItemSkeletonPartial.vue'; + +describe('ArticleItemSkeletonPartial', () => { + it('is animated by default', () => { + const wrapper = mount(ArticleItemSkeletonPartial); + + expect(wrapper.attributes('class')).toContain('animate-pulse'); + }); + + it('can disable the animation', () => { + const wrapper = mount(ArticleItemSkeletonPartial, { + props: { isAnimated: false }, + }); + + expect(wrapper.attributes('class')).not.toContain('animate-pulse'); + }); +}); diff --git a/tests/partials/ArticlesListPartial.test.ts b/tests/partials/ArticlesListPartial.test.ts index 8c6d826c..dabb1e55 100644 --- a/tests/partials/ArticlesListPartial.test.ts +++ b/tests/partials/ArticlesListPartial.test.ts @@ -191,12 +191,12 @@ describe('ArticlesListPartial', () => { // Coalesced or duplicated triggers are OK; we only require that a refresh was scheduled. expect(getPosts.mock.calls.length).toBeGreaterThan(initialGetPostsCalls); - let skeletons = wrapper.findAllComponents(ArticleItemSkeletonPartial); + let skeletons = wrapper.findAll('[aria-busy="true"] article[aria-hidden="true"]'); let attempts = 0; while (skeletons.length !== posts.length && attempts < 10) { await nextTick(); - skeletons = wrapper.findAllComponents(ArticleItemSkeletonPartial); + skeletons = wrapper.findAll('[aria-busy="true"] article[aria-hidden="true"]'); attempts += 1; } diff --git a/tests/partials/PostPageSkeletonPartial.test.ts b/tests/partials/PostPageSkeletonPartial.test.ts new file mode 100644 index 00000000..6baed325 --- /dev/null +++ b/tests/partials/PostPageSkeletonPartial.test.ts @@ -0,0 +1,19 @@ +import { mount } from '@vue/test-utils'; +import { describe, it, expect } from 'vitest'; +import PostPageSkeletonPartial from '@partials/PostPageSkeletonPartial.vue'; + +describe('PostPageSkeletonPartial', () => { + it('exposes a stable test id', () => { + const wrapper = mount(PostPageSkeletonPartial); + + const root = wrapper.get('[data-testid="post-page-skeleton"]'); + expect(root.exists()).toBe(true); + }); + + it('is identified as non-interactive placeholder', () => { + const wrapper = mount(PostPageSkeletonPartial); + + expect(wrapper.attributes('aria-hidden')).toBe('true'); + expect(wrapper.attributes('class')).toContain('animate-pulse'); + }); +}); diff --git a/tests/partials/ProjectCardSkeletonPartial.test.ts b/tests/partials/ProjectCardSkeletonPartial.test.ts new file mode 100644 index 00000000..448b9026 --- /dev/null +++ b/tests/partials/ProjectCardSkeletonPartial.test.ts @@ -0,0 +1,27 @@ +import { mount } from '@vue/test-utils'; +import { describe, it, expect } from 'vitest'; +import ProjectCardSkeletonPartial from '@partials/ProjectCardSkeletonPartial.vue'; + +describe('ProjectCardSkeletonPartial', () => { + it('renders animated wrapper by default', () => { + const wrapper = mount(ProjectCardSkeletonPartial); + + expect(wrapper.attributes('class')).toContain('animate-pulse'); + }); + + it('merges custom wrapper classes', () => { + const wrapper = mount(ProjectCardSkeletonPartial, { + props: { wrapperClass: 'custom-class' }, + }); + + expect(wrapper.classes()).toContain('custom-class'); + }); + + it('disables animation when requested', () => { + const wrapper = mount(ProjectCardSkeletonPartial, { + props: { isAnimated: false }, + }); + + expect(wrapper.attributes('class')).not.toContain('animate-pulse'); + }); +}); diff --git a/tests/partials/TalkCardSkeletonPartial.test.ts b/tests/partials/TalkCardSkeletonPartial.test.ts new file mode 100644 index 00000000..a4d03a6d --- /dev/null +++ b/tests/partials/TalkCardSkeletonPartial.test.ts @@ -0,0 +1,20 @@ +import { mount } from '@vue/test-utils'; +import { describe, it, expect } from 'vitest'; +import TalkCardSkeletonPartial from '@partials/TalkCardSkeletonPartial.vue'; + +describe('TalkCardSkeletonPartial', () => { + it('applies animation by default', () => { + const wrapper = mount(TalkCardSkeletonPartial); + + expect(wrapper.attributes('class')).toContain('animate-pulse'); + expect(wrapper.attributes('aria-hidden')).toBe('true'); + }); + + it('respects disabled animation flag', () => { + const wrapper = mount(TalkCardSkeletonPartial, { + props: { isAnimated: false }, + }); + + expect(wrapper.attributes('class')).not.toContain('animate-pulse'); + }); +}); diff --git a/tests/partials/WidgetSkillsSkeletonPartial.test.ts b/tests/partials/WidgetSkillsSkeletonPartial.test.ts new file mode 100644 index 00000000..408c9134 --- /dev/null +++ b/tests/partials/WidgetSkillsSkeletonPartial.test.ts @@ -0,0 +1,22 @@ +import { mount } from '@vue/test-utils'; +import { describe, it, expect } from 'vitest'; +import WidgetSkillsSkeletonPartial from '@partials/WidgetSkillsSkeletonPartial.vue'; + +describe('WidgetSkillsSkeletonPartial', () => { + it('renders six placeholder skill rows', () => { + const wrapper = mount(WidgetSkillsSkeletonPartial); + const items = wrapper.findAll('li'); + + expect(items).toHaveLength(6); + items.forEach((item) => { + expect(item.classes()).toContain('flex'); + }); + }); + + it('is marked as purely decorative', () => { + const wrapper = mount(WidgetSkillsSkeletonPartial); + + expect(wrapper.attributes('aria-hidden')).toBeUndefined(); + expect(wrapper.attributes('class')).toContain('animate-pulse'); + }); +});