# Tool Shed 2.0 Frontend Vue 3 + TypeScript frontend for the Galaxy Tool Shed. ## Stack - **Framework**: Vue 3 (Composition API + Options API mix) - **Build**: Vite 4 - **UI**: Quasar 2 - **State**: Pinia stores - **API**: openapi-fetch with generated TypeScript types - **Router**: vue-router 4 ## Structure ``` src/ ├── api/ # API wrapper functions ├── components/ # Vue components │ ├── pages/ # Route-level page components │ └── help/ # Help section components ├── schema/ # OpenAPI-generated types + client ├── stores/ # Pinia stores (auth, categories, repository, users) ├── main.ts # App entry ├── router.ts # Vue Router setup └── routes.ts # Route definitions ``` ## Development ```shell # Start dev server (port 4040) pnpm dev # Build pnpm build # Typecheck pnpm typecheck # Lint pnpm lint # Format pnpm format ``` Backend must be running: ```shell # From galaxy root ./run_tool_shed.sh ``` For rapid local dev with bootstrapped data: ```shell TOOL_SHED_CONFIG_OVERRIDE_BOOTSTRAP_ADMIN_API_KEY=tsadminkey \ TOOL_SHED_CONFIG_CONFIG_HG_FOR_DEV=1 \ TOOL_SHED_VITE_PORT=4040 \ ./run_tool_shed.sh ``` ## API Pattern API calls use openapi-fetch typed client via `ToolShedApi()` in `src/schema/client.ts`: ```typescript import { ToolShedApi } from "@/schema" const { data } = await ToolShedApi().GET("/api/repositories", { params: { query: params } }) ``` ## Key Components - `ShedToolbar.vue` - Main navigation toolbar - `RepositoryPage.vue` - Single repository view - `LandingPage.vue` - Homepage - `PaginatedRepositoriesGrid.vue` - Repository listing grid - `ComponentsShowcase.vue` - Developer page for widget demos ## Component Showcase When creating reusable UI components, add examples to `src/components/pages/ComponentsShowcase.vue`. This page (accessible via `/showcase`) helps developers see components in isolation. Pattern: ```vue ``` When writing unit tests that cover special cases (edge cases, error states, long content, special characters, etc.), consider adding those same cases to the Component Showcase. This helps developers: - Visually verify the component handles edge cases correctly - See how the component looks in various states during development - Document expected behavior for different scenarios For example, if you test a component with long text or special characters, add showcase examples demonstrating those cases. ## Path Alias `@/` maps to `src/` directory. ## Accessibility (WCAG 2.1 AA) ### Key Patterns - **Skip link**: `App.vue` - hidden until focused, targets `#main-content` - **Landmarks**: `role="banner"` on header, `role="main"` on page container - **Live regions**: `ErrorBanner.vue` uses `role="alert"`, `LoadingDiv.vue` uses `role="status"` - **Icon buttons**: Use `aria-label` not `title` for accessible names - **Focus indicators**: Global `:focus-visible` styles in `App.vue` ### Components with ARIA | Component | ARIA Attrs | | ------------------------------- | ---------------------------------------------------------- | | `App.vue` | Skip link, landmarks, focus CSS | | `ShedToolbar.vue` | `aria-label` on icon buttons, `aria-haspopup` on dropdowns | | `ErrorBanner.vue` | `role="alert"`, `aria-live="assertive"` | | `LoadingDiv.vue` | `role="status"`, `aria-live="polite"` | | `RepositoryExplore.vue` | `aria-label` on FAB and icon buttons | | `PaginatedRepositoriesGrid.vue` | `aria-label` on table | ### Quasar Notes - `q-btn-dropdown` auto-manages `aria-expanded` - `q-select` has built-in label association - Use `aria-label` on icon-only `q-btn` components - FABs (`q-fab`) need explicit `aria-label` on trigger ### Notification System - `util.ts` `notify()` - uses Quasar Notify (toast messages) - `ErrorBanner.vue` - inline persistent errors with dismiss - `LoadingDiv.vue` - spinner with status message ## Testing (Vitest + Vue Test Utils) ### Setup Tests use Vitest with `@vue/test-utils` for component testing. Test files should be co-located with components (e.g., `MyComponent.vue` → `MyComponent.test.ts`) or in a `__tests__` directory. ### Best Practices for AI-Developed Tests #### 1. Test Behavior, Not Implementation Focus on what the component does from a user's perspective, not internal implementation details: ```typescript // ✅ Good: Tests user-visible behavior test("displays error message when API fails", async () => { vi.mocked(ToolShedApi).mockReturnValue({ error: { status: 500 } }) const wrapper = mount(MyComponent) await flushPromises() expect(wrapper.text()).toContain("Error loading data") }) // ❌ Bad: Tests implementation details test("calls fetchData method", () => { const fetchDataSpy = vi.spyOn(wrapper.vm, "fetchData") // ... }) ``` #### 2. Use Real Queries Over Test IDs Prefer semantic queries (text, labels, roles) over test IDs: ```typescript // ✅ Good: Uses accessible queries const button = wrapper.find('button[aria-label="Submit"]') const error = wrapper.find('[role="alert"]') // ❌ Avoid: Test IDs unless necessary const button = wrapper.find('[data-testid="submit-btn"]') ``` #### 3. Mock External Dependencies Mock API calls, router, and Pinia stores at the module level: ```typescript import { vi } from "vitest" import { ToolShedApi } from "@/schema" vi.mock("@/schema", () => ({ ToolShedApi: vi.fn(), })) test("loads repository data", async () => { vi.mocked(ToolShedApi).mockReturnValue({ GET: vi.fn().mockResolvedValue({ data: { name: "test-repo" } }), }) // ... }) ``` #### 4. Test Composition API Components Properly For Composition API components, use `mount()` and interact with the component as a user would: ```typescript import { mount } from "@vue/test-utils" import { describe, it, expect, vi, beforeEach } from "vitest" import MyComponent from "@/components/MyComponent.vue" describe("MyComponent", () => { it("updates count when button clicked", async () => { const wrapper = mount(MyComponent) const button = wrapper.find("button") await button.trigger("click") expect(wrapper.text()).toContain("Count: 1") }) }) ``` #### 5. Test Options API Components For Options API components, avoid accessing `wrapper.vm` directly. Test through the template: ```typescript // ✅ Good: Tests through template test("shows message prop", () => { const wrapper = mount(MyComponent, { props: { message: "Hello" }, }) expect(wrapper.text()).toContain("Hello") }) // ❌ Avoid: Direct vm access expect(wrapper.vm.message).toBe("Hello") ``` #### 6. Mock Quasar Components When Needed Quasar components can be mocked if they're not the focus of the test: ```typescript import { mount, config } from "@vue/test-utils" config.global.stubs = { "q-btn": { template: "" }, "q-input": { template: "" }, } ``` #### 7. Test Pinia Stores in Isolation Test stores separately from components: ```typescript import { setActivePinia, createPinia } from "pinia" import { useAuthStore } from "@/stores/auth.store" describe("auth store", () => { beforeEach(() => { setActivePinia(createPinia()) }) it("sets user on login", () => { const store = useAuthStore() store.setUser({ id: 1, username: "test" }) expect(store.user?.username).toBe("test") }) }) ``` #### 8. Use `flushPromises()` for Async Operations Wait for async operations to complete: ```typescript import { flushPromises } from "@vue/test-utils" test("loads data asynchronously", async () => { const wrapper = mount(MyComponent) await flushPromises() expect(wrapper.text()).toContain("Loaded") }) ``` #### 9. Test Router Navigation Mock `vue-router` and verify navigation calls: ```typescript import { vi } from "vitest" const mockPush = vi.fn() vi.mock("vue-router", () => ({ useRouter: () => ({ push: mockPush }), })) test("navigates on click", async () => { const wrapper = mount(MyComponent) await wrapper.find("a").trigger("click") expect(mockPush).toHaveBeenCalledWith("/expected-route") }) ``` #### 10. Keep Tests Focused and Independent Each test should verify one behavior and be independent: ```typescript // ✅ Good: Focused test test("displays loading state", () => { const wrapper = mount(MyComponent, { props: { loading: true }, }) expect(wrapper.find('[role="status"]').exists()).toBe(true) }) // ❌ Bad: Multiple concerns test("component does everything", () => { // Tests loading, error, success, navigation... }) ``` #### 11. Use Descriptive Test Names Test names should clearly describe what is being tested: ```typescript // ✅ Good: Clear and descriptive test("displays error banner when API returns 500", async () => {}) test("hides submit button when form is invalid", () => {}) // ❌ Bad: Vague test("works correctly", () => {}) test("component test", () => {}) ``` #### 12. Clean Up After Tests Reset mocks and clear state between tests: ```typescript import { beforeEach, afterEach, vi } from "vitest" beforeEach(() => { vi.clearAllMocks() }) afterEach(() => { vi.restoreAllMocks() }) ``` ### Common Patterns #### Testing Components with Props ```typescript test("renders with required props", () => { const wrapper = mount(MyComponent, { props: { title: "Test Title", count: 5, }, }) expect(wrapper.text()).toContain("Test Title") }) ``` #### Testing User Interactions ```typescript test("calls handler on button click", async () => { const handleClick = vi.fn() const wrapper = mount(MyComponent, { props: { onClick: handleClick }, }) await wrapper.find("button").trigger("click") expect(handleClick).toHaveBeenCalledTimes(1) }) ``` #### Testing Conditional Rendering ```typescript test("shows content when condition is true", () => { const wrapper = mount(MyComponent, { props: { show: true }, }) expect(wrapper.find(".content").exists()).toBe(true) }) test("hides content when condition is false", () => { const wrapper = mount(MyComponent, { props: { show: false }, }) expect(wrapper.find(".content").exists()).toBe(false) }) ``` ### AI-Specific Guidelines When generating tests with AI: 1. **Avoid over-testing**: Don't test every method or computed property. Focus on user-facing behavior. 2. **Don't test framework code**: Vue, Quasar, and Pinia are already tested. Test your application logic. 3. **Test edge cases**: Include tests for error states, empty data, and boundary conditions. 4. **Keep tests maintainable**: If implementation changes, tests should only break if behavior changes. 5. **Use TypeScript**: Leverage type safety to catch errors early. 6. **Mock at the right level**: Mock external services (API, router) but test component logic with real data.