[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"header-categories":3,"post-testing-vue-components-practical":73,"parsed-post-95431d71-6467-4be0-84c0-e9ab7d912c6d":148},{"success":4,"data":5},true,{"items":6,"pagination":70},[7,16,24,32,40,48,56,63],{"id":8,"name":9,"description":10,"parentId":11,"createdAt":12,"updatedAt":12,"parent":11,"children":13,"_count":14},"ai-future","AI & The Future","Artificial intelligence and emerging technology",null,"2026-07-24T06:02:45.990Z",[],{"posts":15},1,{"id":17,"name":18,"description":19,"parentId":11,"createdAt":20,"updatedAt":20,"parent":11,"children":21,"_count":22},"tools-productivity","Tools & Productivity","Developer tools and workflow optimization","2026-07-24T06:02:45.712Z",[],{"posts":23},7,{"id":25,"name":26,"description":27,"parentId":11,"createdAt":28,"updatedAt":28,"parent":11,"children":29,"_count":30},"career","Career & Growth","Professional development for developers","2026-07-24T06:02:45.486Z",[],{"posts":31},3,{"id":33,"name":34,"description":35,"parentId":11,"createdAt":36,"updatedAt":36,"parent":11,"children":37,"_count":38},"opinion","Opinion & Analysis","Industry analysis and technical opinions","2026-07-24T06:02:45.272Z",[],{"posts":39},6,{"id":41,"name":42,"description":43,"parentId":11,"createdAt":44,"updatedAt":44,"parent":11,"children":45,"_count":46},"tutorials","Tutorials","Step-by-step learning guides","2026-07-24T06:02:44.965Z",[],{"posts":47},21,{"id":49,"name":50,"description":51,"parentId":11,"createdAt":52,"updatedAt":52,"parent":11,"children":53,"_count":54},"software-engineering","Software Engineering","Best practices, patterns, and principles","2026-07-24T06:02:44.689Z",[],{"posts":55},24,{"id":57,"name":58,"description":59,"parentId":11,"createdAt":60,"updatedAt":60,"parent":11,"children":61,"_count":62},"devops-cloud","DevOps & Cloud","Infrastructure, deployment, and cloud computing","2026-07-24T06:02:44.453Z",[],{"posts":39},{"id":64,"name":65,"description":66,"parentId":11,"createdAt":67,"updatedAt":67,"parent":11,"children":68,"_count":69},"web-development","Web Development","Frontend and backend web development tutorials and guides","2026-07-24T06:02:44.169Z",[],{"posts":55},{"page":15,"limit":71,"total":71,"totalPages":15,"hasNext":72,"hasPrev":72},8,false,{"success":4,"data":74},{"id":75,"slug":76,"title":77,"excerpt":78,"content":79,"status":80,"visibility":81,"featuredImage":82,"canonicalUrl":11,"readingTime":83,"viewCount":84,"commentEnabled":4,"publishedAt":85,"scheduledAt":11,"createdAt":86,"updatedAt":87,"seoTitle":88,"seoDescription":78,"seoKeywords":11,"authorId":89,"author":90,"coAuthors":95,"tags":96,"categories":112,"comments":115,"_count":116,"relatedPosts":118},"95431d71-6467-4be0-84c0-e9ab7d912c6d","testing-vue-components-practical","Testing Vue Components: A Practical Approach","Stop writing brittle tests. Learn a pragmatic testing strategy for Vue components using Vitest and Vue Test Utils.","## The Testing Philosophy\n\nDon't test implementation details. Test behavior — what the user sees and does.\n\n## Setup\n\n```bash\npnpm add -D vitest @vue\u002Ftest-utils happy-dom\n```\n\n```typescript\n\u002F\u002F vitest.config.ts\nimport { defineConfig } from 'vitest\u002Fconfig'\nimport vue from '@vitejs\u002Fplugin-vue'\n\nexport default defineConfig({\n  plugins: [vue()],\n  test: {\n    environment: 'happy-dom',\n    globals: true,\n  },\n})\n```\n\n## Testing a Button Component\n\n```vue\n\u003C!-- components\u002FAppButton.vue -->\n\u003Ctemplate>\n  \u003Cbutton\n    :disabled=\"loading\"\n    :class=\"{ 'opacity-50': loading }\"\n    @click=\"$emit('click')\"\n  >\n    \u003Cspan v-if=\"loading\">Loading...\u003C\u002Fspan>\n    \u003Cslot v-else \u002F>\n  \u003C\u002Fbutton>\n\u003C\u002Ftemplate>\n```\n\n```typescript\nimport { mount } from '@vue\u002Ftest-utils'\nimport AppButton from '.\u002FAppButton.vue'\n\ndescribe('AppButton', () => {\n  it('renders slot content', () => {\n    const wrapper = mount(AppButton, {\n      slots: { default: 'Click me' },\n    })\n    expect(wrapper.text()).toBe('Click me')\n  })\n\n  it('shows loading state', () => {\n    const wrapper = mount(AppButton, {\n      props: { loading: true },\n    })\n    expect(wrapper.text()).toBe('Loading...')\n    expect(wrapper.attributes('disabled')).toBeDefined()\n  })\n\n  it('emits click event', async () => {\n    const wrapper = mount(AppButton, {\n      slots: { default: 'Click' },\n    })\n    await wrapper.trigger('click')\n    expect(wrapper.emitted('click')).toHaveLength(1)\n  })\n})\n```\n\n## Testing Composables\n\n```typescript\nimport { useCounter } from '.\u002FuseCounter'\n\ndescribe('useCounter', () => {\n  it('increments count', () => {\n    const { count, increment } = useCounter()\n    expect(count.value).toBe(0)\n    increment()\n    expect(count.value).toBe(1)\n  })\n})\n```\n\n::callout{icon=\"i-lucide-target\" color=\"primary\"}\nThe testing pyramid for Vue: many composable\u002Futility unit tests, some component integration tests, few E2E tests. Composables are the easiest to test thoroughly.\n::\n\n## Testing Async Components\n\n```typescript\nimport { flushPromises, mount } from '@vue\u002Ftest-utils'\nimport UserProfile from '.\u002FUserProfile.vue'\n\n\u002F\u002F Mock the fetch\nvi.mock('~\u002Fcomposables\u002FuseApi', () => ({\n  useApi: () => ({\n    data: ref({ name: 'Mbeah', email: 'mbeah@test.com' }),\n    loading: ref(false),\n    error: ref(null),\n  }),\n}))\n\ndescribe('UserProfile', () => {\n  it('displays user data', async () => {\n    const wrapper = mount(UserProfile)\n    await flushPromises()\n    expect(wrapper.text()).toContain('Mbeah')\n  })\n})\n```\n\n## What NOT to Test\n\n- Third-party library internals\n- CSS classes (unless they represent state)\n- Exact DOM structure\n- Private methods or internal state\n\nTest the contract: props in, events and rendered output out.\n","published","public","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1633356122544-f134324a6cee?w=1200&q=80",9,2077,"2026-07-01T06:03:14.150Z","2026-07-24T06:03:14.153Z","2026-07-28T17:10:27.405Z","Testing Vue Components: A Practical Approach | BitBlog","fddb5d93-7a2c-4d86-a06a-fa32e73a01c6",{"email":91,"bio":92,"id":89,"name":93,"avatarUrl":94},"mbeahessilfieprince@gmail.com","Fullstack Software Developer ","Mbeah Essilfie","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F93322394?v=4",[],[97,102,107],{"id":98,"name":99,"color":100,"description":101},"typescript","TypeScript","#3178c6","Typed superset of JavaScript",{"id":103,"name":104,"color":105,"description":106},"vue","Vue","#4fc08d","The Progressive JavaScript Framework",{"id":108,"name":109,"color":110,"description":111},"testing","Testing","#99425b","Software testing practices",[113,114],{"id":49,"name":50,"description":51},{"id":41,"name":42,"description":43},[],{"comments":117},0,[119,128,138],{"id":120,"slug":121,"title":122,"excerpt":123,"featuredImage":124,"viewCount":125,"readingTime":71,"publishedAt":126,"author":127},"2997028f-4d22-4eb7-9d86-894a54cb559a","building-type-safe-apis-nuxt3-prisma","Building Type-Safe APIs with Nuxt 3 and Prisma","Learn how to build end-to-end type-safe APIs using Nuxt 3 server routes and Prisma ORM for a seamless developer experience.","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1498050108023-c5249f4df085?w=1200&q=80",1922,"2026-07-23T06:02:46.910Z",{"id":89,"name":93,"avatarUrl":94},{"id":129,"slug":130,"title":131,"excerpt":132,"featuredImage":133,"viewCount":134,"readingTime":135,"publishedAt":136,"author":137},"da9e4553-8b0d-411e-b4ec-f9684017e163","mastering-tailwind-css-design-systems","Mastering Tailwind CSS: From Utility Classes to Design Systems","Go beyond basic utility classes and learn how to build cohesive, maintainable design systems with Tailwind CSS.","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1555066931-4365d14bab8c?w=1200&q=80",257,10,"2026-07-21T06:02:49.268Z",{"id":89,"name":93,"avatarUrl":94},{"id":139,"slug":140,"title":141,"excerpt":142,"featuredImage":143,"viewCount":144,"readingTime":145,"publishedAt":146,"author":147},"50add5fc-4026-4267-b917-7f71ea9e35b0","complete-guide-vue3-composables","The Complete Guide to Vue 3 Composables","Understand the power of Vue 3's Composition API through practical, real-world composable patterns that you can use today.","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1517694712202-14dd9538aa97?w=1200&q=80",990,12,"2026-07-19T06:02:51.753Z",{"id":89,"name":93,"avatarUrl":94},{"data":149,"body":151,"toc":300},{"title":150,"description":150},"",{"type":152,"children":153},"root",[154,163,169,175,188,197,203,212,220,226,234,245,251,259,265,290,295],{"type":155,"tag":156,"props":157,"children":159},"element","h2",{"id":158},"the-testing-philosophy",[160],{"type":161,"value":162},"text","The Testing Philosophy",{"type":155,"tag":164,"props":165,"children":166},"p",{},[167],{"type":161,"value":168},"Don't test implementation details. Test behavior — what the user sees and does.",{"type":155,"tag":156,"props":170,"children":172},{"id":171},"setup",[173],{"type":161,"value":174},"Setup",{"type":155,"tag":176,"props":177,"children":182},"pre",{"className":178,"code":179,"language":180,"meta":150,"style":181},"language-bash","pnpm add -D vitest @vue\u002Ftest-utils happy-dom\n","bash","undefined",[183],{"type":155,"tag":184,"props":185,"children":186},"code",{"__ignoreMap":150},[187],{"type":161,"value":179},{"type":155,"tag":176,"props":189,"children":192},{"className":190,"code":191,"language":98,"meta":150,"style":181},"language-typescript","\u002F\u002F vitest.config.ts\nimport { defineConfig } from 'vitest\u002Fconfig'\nimport vue from '@vitejs\u002Fplugin-vue'\n\nexport default defineConfig({\n  plugins: [vue()],\n  test: {\n    environment: 'happy-dom',\n    globals: true,\n  },\n})\n",[193],{"type":155,"tag":184,"props":194,"children":195},{"__ignoreMap":150},[196],{"type":161,"value":191},{"type":155,"tag":156,"props":198,"children":200},{"id":199},"testing-a-button-component",[201],{"type":161,"value":202},"Testing a Button Component",{"type":155,"tag":176,"props":204,"children":207},{"className":205,"code":206,"language":103,"meta":150,"style":181},"language-vue","\u003C!-- components\u002FAppButton.vue -->\n\u003Ctemplate>\n  \u003Cbutton\n    :disabled=\"loading\"\n    :class=\"{ 'opacity-50': loading }\"\n    @click=\"$emit('click')\"\n  >\n    \u003Cspan v-if=\"loading\">Loading...\u003C\u002Fspan>\n    \u003Cslot v-else \u002F>\n  \u003C\u002Fbutton>\n\u003C\u002Ftemplate>\n",[208],{"type":155,"tag":184,"props":209,"children":210},{"__ignoreMap":150},[211],{"type":161,"value":206},{"type":155,"tag":176,"props":213,"children":215},{"className":190,"code":214,"language":98,"meta":150,"style":181},"import { mount } from '@vue\u002Ftest-utils'\nimport AppButton from '.\u002FAppButton.vue'\n\ndescribe('AppButton', () => {\n  it('renders slot content', () => {\n    const wrapper = mount(AppButton, {\n      slots: { default: 'Click me' },\n    })\n    expect(wrapper.text()).toBe('Click me')\n  })\n\n  it('shows loading state', () => {\n    const wrapper = mount(AppButton, {\n      props: { loading: true },\n    })\n    expect(wrapper.text()).toBe('Loading...')\n    expect(wrapper.attributes('disabled')).toBeDefined()\n  })\n\n  it('emits click event', async () => {\n    const wrapper = mount(AppButton, {\n      slots: { default: 'Click' },\n    })\n    await wrapper.trigger('click')\n    expect(wrapper.emitted('click')).toHaveLength(1)\n  })\n})\n",[216],{"type":155,"tag":184,"props":217,"children":218},{"__ignoreMap":150},[219],{"type":161,"value":214},{"type":155,"tag":156,"props":221,"children":223},{"id":222},"testing-composables",[224],{"type":161,"value":225},"Testing Composables",{"type":155,"tag":176,"props":227,"children":229},{"className":190,"code":228,"language":98,"meta":150,"style":181},"import { useCounter } from '.\u002FuseCounter'\n\ndescribe('useCounter', () => {\n  it('increments count', () => {\n    const { count, increment } = useCounter()\n    expect(count.value).toBe(0)\n    increment()\n    expect(count.value).toBe(1)\n  })\n})\n",[230],{"type":155,"tag":184,"props":231,"children":232},{"__ignoreMap":150},[233],{"type":161,"value":228},{"type":155,"tag":235,"props":236,"children":239},"callout",{"color":237,"icon":238},"primary","i-lucide-target",[240],{"type":155,"tag":164,"props":241,"children":242},{},[243],{"type":161,"value":244},"The testing pyramid for Vue: many composable\u002Futility unit tests, some component integration tests, few E2E tests. Composables are the easiest to test thoroughly.",{"type":155,"tag":156,"props":246,"children":248},{"id":247},"testing-async-components",[249],{"type":161,"value":250},"Testing Async Components",{"type":155,"tag":176,"props":252,"children":254},{"className":190,"code":253,"language":98,"meta":150,"style":181},"import { flushPromises, mount } from '@vue\u002Ftest-utils'\nimport UserProfile from '.\u002FUserProfile.vue'\n\n\u002F\u002F Mock the fetch\nvi.mock('~\u002Fcomposables\u002FuseApi', () => ({\n  useApi: () => ({\n    data: ref({ name: 'Mbeah', email: 'mbeah@test.com' }),\n    loading: ref(false),\n    error: ref(null),\n  }),\n}))\n\ndescribe('UserProfile', () => {\n  it('displays user data', async () => {\n    const wrapper = mount(UserProfile)\n    await flushPromises()\n    expect(wrapper.text()).toContain('Mbeah')\n  })\n})\n",[255],{"type":155,"tag":184,"props":256,"children":257},{"__ignoreMap":150},[258],{"type":161,"value":253},{"type":155,"tag":156,"props":260,"children":262},{"id":261},"what-not-to-test",[263],{"type":161,"value":264},"What NOT to Test",{"type":155,"tag":266,"props":267,"children":268},"ul",{},[269,275,280,285],{"type":155,"tag":270,"props":271,"children":272},"li",{},[273],{"type":161,"value":274},"Third-party library internals",{"type":155,"tag":270,"props":276,"children":277},{},[278],{"type":161,"value":279},"CSS classes (unless they represent state)",{"type":155,"tag":270,"props":281,"children":282},{},[283],{"type":161,"value":284},"Exact DOM structure",{"type":155,"tag":270,"props":286,"children":287},{},[288],{"type":161,"value":289},"Private methods or internal state",{"type":155,"tag":164,"props":291,"children":292},{},[293],{"type":161,"value":294},"Test the contract: props in, events and rendered output out.",{"type":155,"tag":296,"props":297,"children":298},"style",{},[299],{"type":161,"value":150},{"title":150,"searchDepth":301,"depth":301,"links":302},2,[303,304,305,306,307,308],{"id":158,"depth":301,"text":162},{"id":171,"depth":301,"text":174},{"id":199,"depth":301,"text":202},{"id":222,"depth":301,"text":225},{"id":247,"depth":301,"text":250},{"id":261,"depth":301,"text":264}]