@gallopsystems/agent-skills 1.11.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/plugins/git-github/skills/git-github/SKILL.md +1 -0
- package/plugins/nitro-testing/skills/nitro-testing/frontend-testing.md +40 -0
- package/plugins/vue-nuxt/skills/vue-nuxt/SKILL.md +5 -0
- package/plugins/vue-nuxt/skills/vue-nuxt/vueuse.md +13 -5
- package/plugins/vue-nuxt/skills/vue-nuxt/watch.md +6 -6
package/package.json
CHANGED
|
@@ -51,6 +51,7 @@ EOF
|
|
|
51
51
|
- After merge: `git switch main && git pull --ff-only`, clean up `[gone]` branches, start the next branch from fresh main.
|
|
52
52
|
- One concern per PR — hotfixes and review findings go in separate PRs unless told otherwise.
|
|
53
53
|
- Stacked PRs: `gh pr create --base <parent-branch>`; after the parent merges, retarget with `gh pr edit <n> --base main` (and see [getting-unstuck.md](getting-unstuck.md) for rebasing onto main after the parent was squash-merged).
|
|
54
|
+
- If you discover uncommitted work on the wrong branch and the PR must be "off main", do not commit to the wrong branch. With a cleanly applicable worktree, `git fetch origin main && git switch -c feat/<short-description> origin/main` carries the unstaged changes onto a new branch from `origin/main`. Verify with `git status` and tests. If checkout would overwrite/conflict, stash with `-u`. Only resort to worktree if stash gets too complicated.
|
|
54
55
|
|
|
55
56
|
## Reading PR and CI State
|
|
56
57
|
|
|
@@ -526,6 +526,19 @@ await flushPromises();
|
|
|
526
526
|
expect(replace.mock.calls.some((c) => (c[0] as any)?.query?.q === "foo")).toBe(true);
|
|
527
527
|
```
|
|
528
528
|
|
|
529
|
+
If the component's source of truth is `useRoute()`/`useRouter()`, `route:` is not
|
|
530
|
+
always the most direct test seam: app middleware, route rules, and redirects still
|
|
531
|
+
run. For component-level behavior, mock the Nuxt imports before mounting:
|
|
532
|
+
|
|
533
|
+
```typescript
|
|
534
|
+
mockNuxtImport("useRoute", () => () => ({ query: { tab: "activity" }, params: {} }));
|
|
535
|
+
mockNuxtImport("useRouter", () => () => ({ replace: vi.fn(), push: vi.fn() }));
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
If the component imports router helpers directly from `vue-router`, mock that
|
|
539
|
+
module too. Keep the test focused on what the component reads or writes; use an
|
|
540
|
+
end-to-end/page test when the middleware behavior itself is under test.
|
|
541
|
+
|
|
529
542
|
### 9. Testing a composable that needs a component scope
|
|
530
543
|
|
|
531
544
|
For a composable using lifecycle hooks or `provide`/`inject`, mount a throwaway
|
|
@@ -541,6 +554,12 @@ Control a VueUse dependency with `vi.mock("@vueuse/core", …)` to return refs y
|
|
|
541
554
|
drive. And make sure the vitest `include` glob covers `app/composables/**` — a
|
|
542
555
|
composables dir is easy to leave out of the frontend config.
|
|
543
556
|
|
|
557
|
+
Do this for lifecycle composables even when the return value looks plain:
|
|
558
|
+
`onMounted`, `onUnmounted`, `useEventListener`, `useScrollLock`, and similar
|
|
559
|
+
helpers need a component effect scope to attach and clean up correctly. Calling
|
|
560
|
+
the composable directly in a Vitest test can produce Vue warnings and, worse,
|
|
561
|
+
skip the listener or cleanup path you meant to verify.
|
|
562
|
+
|
|
544
563
|
### 10. Reset the shared `useFetch` cache between mounts
|
|
545
564
|
|
|
546
565
|
`useFetch`/`useAsyncData` cache by key, and the cache is **shared across
|
|
@@ -553,6 +572,27 @@ import { clearNuxtData } from "#imports"; // not exported from @nuxt/test-utils/
|
|
|
553
572
|
beforeEach(() => clearNuxtData());
|
|
554
573
|
```
|
|
555
574
|
|
|
575
|
+
### 11. Mock Nuxt fetch behavior at the right layer
|
|
576
|
+
|
|
577
|
+
`mockNuxtImport("$fetch", ...)` is not a reliable target: `$fetch` is not a normal
|
|
578
|
+
Nuxt auto-import in the same way `useRoute` or a composable is, so the transform
|
|
579
|
+
may fail before the test even runs. Prefer `registerEndpoint` when the component
|
|
580
|
+
calls `useFetch`, `useAsyncData`, or `$fetch` against an app route:
|
|
581
|
+
|
|
582
|
+
```typescript
|
|
583
|
+
registerEndpoint("/api/search", {
|
|
584
|
+
method: "GET",
|
|
585
|
+
handler: (event) => {
|
|
586
|
+
const q = new URL(event.node.req.url!, "http://localhost").searchParams.get("q");
|
|
587
|
+
return [{ id: 1, name: q ?? "" }];
|
|
588
|
+
},
|
|
589
|
+
});
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
If the component calls a local service/composable that wraps `$fetch`, mock that
|
|
593
|
+
service/composable instead. Mock the boundary you own; use `registerEndpoint` for
|
|
594
|
+
Nuxt's fetch path.
|
|
595
|
+
|
|
556
596
|
## File Organization
|
|
557
597
|
|
|
558
598
|
Co-locate tests with source files:
|
|
@@ -39,6 +39,11 @@ cross-links to those rather than restating them.
|
|
|
39
39
|
- [page-structure.md](./page-structure.md) — keep pages thin: route-param parsing + layout in the page, data/logic/forms in components
|
|
40
40
|
- [formatters.md](./formatters.md) — never inline a currency/date/number formatter; centralize in `useFormatters`, prefer Intl/date-fns
|
|
41
41
|
|
|
42
|
+
Testing note: when a Vue/Nuxt refactor changes component behavior, route/query
|
|
43
|
+
state, or a composable with lifecycle hooks, use the Nuxt frontend testing
|
|
44
|
+
guidance in `nitro-testing`'s [frontend-testing.md](../../../../nitro-testing/skills/nitro-testing/frontend-testing.md)
|
|
45
|
+
instead of testing those pieces as plain Vue functions.
|
|
46
|
+
|
|
42
47
|
## Core Principles
|
|
43
48
|
|
|
44
49
|
1. **Lean on auto-imports.** `app/components`, `app/composables`, `app/utils`, and the Vue/Nuxt APIs all auto-import. Add an explicit `import` only for third-party symbols and TS types. A nested component's tag carries its directory as a prefix (`components/customers/ProfileCard.vue` → `<CustomersProfileCard>`).
|
|
@@ -24,6 +24,9 @@ export default defineNuxtConfig({ modules: ['@vueuse/nuxt'] })
|
|
|
24
24
|
|
|
25
25
|
The **`@vueuse/router`** and **`@vueuse/integrations`** add-ons are separate
|
|
26
26
|
installs and are **not** auto-imported by the module — import them explicitly.
|
|
27
|
+
For URL query sync, install `@vueuse/router` and use its router composables before
|
|
28
|
+
hand-rolling `useRoute()`/`useRouter()` glue; the package exists exactly for that
|
|
29
|
+
boundary.
|
|
27
30
|
|
|
28
31
|
## The boundary cases (mapped from `watch.md`)
|
|
29
32
|
|
|
@@ -42,8 +45,9 @@ installs and are **not** auto-imported by the module — import them explicitly.
|
|
|
42
45
|
|
|
43
46
|
URL sync (`router.replace({ query: { ...route.query, tab } })`) →
|
|
44
47
|
`useRouteQuery('tab')` from `@vueuse/router` gives a ref bound two-way to the query
|
|
45
|
-
param. Nuxt's own `useRoute()` is already reactive for *reads*;
|
|
46
|
-
`useRouteQuery` when you want a **writable** ref bound to a single param
|
|
48
|
+
param. Nuxt's own `useRoute()` is already reactive for *reads*; install and import
|
|
49
|
+
`useRouteQuery` when you want a **writable** ref bound to a single param instead of
|
|
50
|
+
open-coding the same replace/query merge logic.
|
|
47
51
|
|
|
48
52
|
### `useLocalStorage` reads at setup — guard SSR hydration
|
|
49
53
|
|
|
@@ -67,9 +71,13 @@ Because the bound ref reads from and writes to the query param, the URL *is* the
|
|
|
67
71
|
state — so it **can't represent a ref with two distinct "empty" states** (e.g. a
|
|
68
72
|
filter that defaults to `"Open"` on load but clears to `null`; both would be "param
|
|
69
73
|
absent"). When you need that distinction, keep a plain `ref` + a projecting `watch`
|
|
70
|
-
(a sanctioned `watch.md` "URL sync" case).
|
|
71
|
-
|
|
72
|
-
|
|
74
|
+
(a sanctioned `watch.md` "URL sync" case). The same exception applies when one
|
|
75
|
+
source object fans out to several query params and the projection itself is the
|
|
76
|
+
behavior being tested. Otherwise, adding `@vueuse/router` is preferable to writing
|
|
77
|
+
your own route-query synchronization. And **don't mix `useUrlSearchParams`
|
|
78
|
+
(History API) with `useRouteQuery` (vue-router) in the same component** — they
|
|
79
|
+
write the URL through different mechanisms and clobber each other's params; pick
|
|
80
|
+
one.
|
|
73
81
|
|
|
74
82
|
## VueUse's `watch` sugar — when a `watch` IS warranted
|
|
75
83
|
|
|
@@ -69,12 +69,12 @@ Only when the effect crosses **out of** the reactive graph:
|
|
|
69
69
|
- **Persist** — a `localStorage`/`useCookie` write, debounced auto-save of a
|
|
70
70
|
deep-watched form.
|
|
71
71
|
- **URL sync** — `router.replace({ query: { ...route.query, tab } })`. For a single
|
|
72
|
-
ref ↔ one query param, prefer `useRouteQuery` (see
|
|
73
|
-
the right tool only when the URL can't model the
|
|
74
|
-
"empty" states** (e.g. a filter that defaults to
|
|
75
|
-
`null` — an absent param can map to only one of
|
|
76
|
-
fanning out to many params** (a PrimeVue filter
|
|
77
|
-
`useRouteQuery` can't express.
|
|
72
|
+
ref ↔ one query param, install `@vueuse/router` and prefer `useRouteQuery` (see
|
|
73
|
+
below). A write-back watch is the right tool only when the URL can't model the
|
|
74
|
+
state: a ref with **two distinct "empty" states** (e.g. a filter that defaults to
|
|
75
|
+
`"Open"` on load but clears to `null` — an absent param can map to only one of
|
|
76
|
+
them), or a **composite object fanning out to many params** (a PrimeVue filter
|
|
77
|
+
object) that a one-ref-per-param `useRouteQuery` can't express.
|
|
78
78
|
- **Re-seed local state on dialog open** — `watch(visible, (v) => { if (v) initForm() })`.
|
|
79
79
|
The single most common legit pattern. Its one-liner is `whenever(visible, initForm)`
|
|
80
80
|
(see `vueuse.md`); a compound guard keeps its inner half —
|