@lariocpt/flawless-react 0.0.0-stage → 0.3.1
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/.agents/skills/flawless-a11y/SKILL.md +113 -0
- package/.agents/skills/flawless-api/SKILL.md +159 -0
- package/.agents/skills/flawless-api-fixtures/SKILL.md +144 -0
- package/.agents/skills/flawless-e2e/SKILL.md +155 -0
- package/.agents/skills/flawless-expo/SKILL.md +283 -0
- package/.agents/skills/flawless-findings/SKILL.md +116 -0
- package/.agents/skills/flawless-load/SKILL.md +112 -0
- package/.agents/skills/flawless-nextjs/SKILL.md +198 -0
- package/.agents/skills/flawless-perf-harness/SKILL.md +158 -0
- package/.agents/skills/flawless-perf-static/SKILL.md +131 -0
- package/.agents/skills/flawless-react/SKILL.md +403 -0
- package/.agents/skills/flawless-react-rules/SKILL.md +96 -0
- package/.agents/skills/flawless-regen/SKILL.md +135 -0
- package/.agents/skills/flawless-unit-browser/SKILL.md +178 -0
- package/LICENSE +21 -0
- package/README.md +611 -2
- package/bin/flawless-react +4 -0
- package/package.json +67 -4
- package/scripts/postinstall.d.mts +46 -0
- package/scripts/postinstall.mjs +127 -0
- package/src/analyse/authShims.ts +220 -0
- package/src/analyse/bootability.ts +257 -0
- package/src/analyse/childBridge.ts +302 -0
- package/src/analyse/childrenIntoSlot.ts +66 -0
- package/src/analyse/compoundFamily.ts +100 -0
- package/src/analyse/dataExports.ts +200 -0
- package/src/analyse/extractChildAttrs.ts +112 -0
- package/src/analyse/extractDataDeps.ts +857 -0
- package/src/analyse/extractDisclosures.ts +186 -0
- package/src/analyse/extractFetchCalls.ts +81 -0
- package/src/analyse/extractFlows.ts +239 -0
- package/src/analyse/extractGuards.ts +270 -0
- package/src/analyse/extractHooks.ts +237 -0
- package/src/analyse/extractInteractions.ts +1421 -0
- package/src/analyse/extractLogging.ts +45 -0
- package/src/analyse/extractNativeA11y.ts +138 -0
- package/src/analyse/extractPredicates.ts +784 -0
- package/src/analyse/extractPropUsage.ts +162 -0
- package/src/analyse/extractProps.ts +1212 -0
- package/src/analyse/extractRenderMode.ts +165 -0
- package/src/analyse/extractRootTag.ts +117 -0
- package/src/analyse/extractState.ts +361 -0
- package/src/analyse/extractStaticText.ts +212 -0
- package/src/analyse/extractVariants.ts +186 -0
- package/src/analyse/harvestApiEndpoints.ts +284 -0
- package/src/analyse/hostProps.ts +185 -0
- package/src/analyse/hostTag.ts +72 -0
- package/src/analyse/htmlContentModel.ts +94 -0
- package/src/analyse/index.ts +744 -0
- package/src/analyse/mountPathCalls.ts +442 -0
- package/src/analyse/nativeContentModel.ts +129 -0
- package/src/analyse/nativeDialect.ts +83 -0
- package/src/analyse/nativeProps.ts +339 -0
- package/src/analyse/ownReturns.ts +31 -0
- package/src/analyse/parseFile.ts +919 -0
- package/src/analyse/pos.ts +8 -0
- package/src/analyse/providerWraps.ts +1977 -0
- package/src/analyse/pureModule.ts +88 -0
- package/src/analyse/reexportTable.ts +148 -0
- package/src/analyse/refs.ts +195 -0
- package/src/analyse/renderPath.ts +441 -0
- package/src/analyse/resolveDepReturns.ts +370 -0
- package/src/analyse/resolveIndexKeys.ts +98 -0
- package/src/analyse/resolveModule.ts +431 -0
- package/src/analyse/resolveTagRootTags.ts +103 -0
- package/src/analyse/serverActionReturns.ts +213 -0
- package/src/analyse/sourceCache.ts +263 -0
- package/src/analyse/spreadSiblings.ts +98 -0
- package/src/analyse/walkChildren.ts +98 -0
- package/src/analyse/zodSchema.ts +424 -0
- package/src/bin.ts +23 -0
- package/src/changed.ts +221 -0
- package/src/cli.ts +560 -0
- package/src/cliFlags.ts +281 -0
- package/src/core.ts +1229 -0
- package/src/docLine.ts +122 -0
- package/src/emitPaths.ts +51 -0
- package/src/expo/detect.ts +184 -0
- package/src/expo/refusal.ts +62 -0
- package/src/expo/routes.ts +145 -0
- package/src/index.ts +95 -0
- package/src/mcp/analysisTools.ts +720 -0
- package/src/mcp/infoTools.ts +135 -0
- package/src/mcp/protocol.ts +130 -0
- package/src/mcp/run.ts +269 -0
- package/src/mcp/runTools.ts +362 -0
- package/src/mcp/server.ts +279 -0
- package/src/mcp/spec.ts +86 -0
- package/src/mcp/support.ts +268 -0
- package/src/mcp/tools.ts +75 -0
- package/src/nextjs/apiRoutes.ts +116 -0
- package/src/nextjs/detect.ts +397 -0
- package/src/nextjs/emit.ts +106 -0
- package/src/nextjs/liveSpec.ts +79 -0
- package/src/nextjs/mswHandlers.ts +55 -0
- package/src/nextjs/providerWrapper.ts +603 -0
- package/src/nextjs/routes.ts +86 -0
- package/src/nextjs/swagger.ts +32 -0
- package/src/packageJson.ts +414 -0
- package/src/platform.ts +18 -0
- package/src/prompt.ts +132 -0
- package/src/reactCompiler.ts +165 -0
- package/src/reporters/findings.ts +156 -0
- package/src/reporters/report.ts +463 -0
- package/src/runner/runTests.ts +402 -0
- package/src/scaffold/adoptStyle.ts +181 -0
- package/src/scaffold/index.ts +253 -0
- package/src/scaffold/openapi.ts +183 -0
- package/src/scaffold/templates.ts +2379 -0
- package/src/scaffold-tests/index.ts +463 -0
- package/src/scaffold-tests/mockValues.ts +208 -0
- package/src/scaffold-tests/mocks.ts +794 -0
- package/src/scaffold-tests/nativeIndex.ts +52 -0
- package/src/scaffold-tests/nativeTemplates.ts +1528 -0
- package/src/scaffold-tests/postDispatch.ts +152 -0
- package/src/scaffold-tests/registry.ts +443 -0
- package/src/scaffold-tests/selectors.ts +493 -0
- package/src/scaffold-tests/selectorsNative.ts +273 -0
- package/src/scaffold-tests/stamp.ts +79 -0
- package/src/scaffold-tests/templates.ts +3578 -0
- package/src/skills/cli.ts +248 -0
- package/src/skills/compat.ts +166 -0
- package/src/skills/data.ts +192 -0
- package/src/skills/md.d.ts +7 -0
- package/src/skills/menu.ts +55 -0
- package/src/skills/spec.ts +227 -0
- package/src/suiteScripts.ts +110 -0
- package/src/tasks/a11yNative.ts +281 -0
- package/src/tasks/a11yScan.ts +67 -0
- package/src/tasks/biomeRunner.ts +301 -0
- package/src/tasks/biomeSweep.ts +166 -0
- package/src/tasks/e2eExpo.ts +436 -0
- package/src/tasks/e2eRoutes.ts +756 -0
- package/src/tasks/eslintReactRules.ts +67 -0
- package/src/tasks/index.ts +24 -0
- package/src/tasks/loadExpo.ts +273 -0
- package/src/tasks/loadPipes.ts +750 -0
- package/src/tasks/perfHarness.ts +145 -0
- package/src/tasks/staticPerfAnalysis.ts +510 -0
- package/src/tasks/types.ts +1 -0
- package/src/tasks/unitAndBrowser.ts +462 -0
- package/src/types.ts +1419 -0
- package/src/version.ts +12 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flawless-unit-browser
|
|
3
|
+
description: Work with flawless-react's generated unit and browser specs — what they assert, the provider wrapper and its write-once fixtures, the mock allowlist, answering test.skip and TODO(flawless) markers, hand-hardening colocated specs, and registering new components in the browser host. Use when a generated unit/browser spec is red, when filling TODOs after a run, or when a component's browser spec reports "unknown component".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# flawless unit + browser specs
|
|
7
|
+
|
|
8
|
+
> **Expo projects use a different plane entirely.** Under `--expo` this task emits
|
|
9
|
+
> jest-expo + @testing-library/react-native specs into `<outDir>/unit/`, with
|
|
10
|
+
> `renderRouter` for route screens, no browser half at all, and a selector ladder that
|
|
11
|
+
> has no last-resort rung. Everything below describes the **DOM** plane — see the
|
|
12
|
+
> `flawless-expo` skill for the native one.
|
|
13
|
+
|
|
14
|
+
The `unit-browser` task writes Vitest + Testing Library specs from static
|
|
15
|
+
analysis; when `perf-harness` is also selected it adds Playwright browser specs
|
|
16
|
+
that mount through the profiling host. Everything asserted was derived — the
|
|
17
|
+
specs describe the render at the props they pass, never the last `return` in
|
|
18
|
+
the body.
|
|
19
|
+
|
|
20
|
+
## The provider wrapper and its fixtures
|
|
21
|
+
|
|
22
|
+
Unit specs mount inside `flawless/provider.tsx`, derived from the layout's JSX
|
|
23
|
+
chain around `{children}`. That file is regenerated; the values you supply live
|
|
24
|
+
in `flawless/provider.fixtures.ts`, which is written once and never again —
|
|
25
|
+
not even with `--force`.
|
|
26
|
+
|
|
27
|
+
Plain (non-Next) mode has no `provider.tsx`: the wraps are derived per render
|
|
28
|
+
instead. Each spec imports its nest under `__Prov`-prefixed bindings — the
|
|
29
|
+
app's own providers found by hook call, namespace compound, transitive
|
|
30
|
+
requirement and dynamic render (a `<Component />` whose tag a project hook
|
|
31
|
+
handed back: `const { Component } = useTabs()` — the component owes what the
|
|
32
|
+
hook's values can hand out, the hook body's own JSX plus one import hop into
|
|
33
|
+
the registry it selects from), convention matches for bare-module hooks (`useTheme` → the
|
|
34
|
+
exported `ThemeProvider` that renders that module's binding), and generated
|
|
35
|
+
shims for library contexts (`FlawlessQueryClientProvider`,
|
|
36
|
+
`FlawlessRouterProvider`/`FlawlessDataRouterProvider`, `FlawlessFormProvider`,
|
|
37
|
+
`FlawlessIntlProvider`). A mount failing on "useX must be used within a
|
|
38
|
+
XProvider" in plain mode means the derivation could not see the provider:
|
|
39
|
+
check that it is exported under the convention name and renders a binding from
|
|
40
|
+
the hook's own module; if the app side cannot be fixed, mount it with
|
|
41
|
+
`--provider`, which renders outside the derived nest.
|
|
42
|
+
|
|
43
|
+
The single highest-value hardening move: fill `initialState` (or whatever the
|
|
44
|
+
layout passed) with a REAL seeded shape instead of the emitted empty cast.
|
|
45
|
+
Mirror the app's seed data so every unit spec mounts the way the app actually
|
|
46
|
+
renders — authenticated, themed, populated:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
initialState: {
|
|
50
|
+
user: { id: "…", email: "trustee@demo.local", name: "…", /* seed persona */ },
|
|
51
|
+
status: "authenticated",
|
|
52
|
+
} satisfies ComponentProps<typeof StoreProvider>["initialState"],
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Pin any timestamp in the fixture — `Date.now()` in a fixture makes specs
|
|
56
|
+
nondeterministic.
|
|
57
|
+
|
|
58
|
+
## The mock allowlist, and the cn caveat
|
|
59
|
+
|
|
60
|
+
Client specs mock only `next/navigation`, `next/headers`, `server-only`,
|
|
61
|
+
`next/link`, `next/font/*`, and `"use server"` modules. Do not widen it by
|
|
62
|
+
hand: mocking a UI component replaces the element the interaction tests click.
|
|
63
|
+
|
|
64
|
+
Historical wart, fixed in FR-027 but still present in suites generated before
|
|
65
|
+
it: specs whose file imports `cn` from a utils module may carry
|
|
66
|
+
`vi.mock("@/lib/utils", () => ({ cn: vi.fn(async () => ({})) }))` — an async
|
|
67
|
+
stub for a sync function. A regeneration emits the sync form; to fix an old
|
|
68
|
+
spec in place, replace it with a sync join so classes stay legible:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
vi.mock("@/lib/utils", () => ({
|
|
72
|
+
cn: vi.fn((...args: unknown[]) => args.filter(Boolean).join(" ")),
|
|
73
|
+
}));
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Answering skips and TODOs
|
|
77
|
+
|
|
78
|
+
`test.skip` with a named reason is intended output — never "fix" it by deleting
|
|
79
|
+
the assertion or hand-writing one. Give the component what the reason asks for
|
|
80
|
+
(a `data-testid`, a fixture) or extend the generator. `TODO(flawless)` markers
|
|
81
|
+
cluster into three kinds, each with a known answer:
|
|
82
|
+
|
|
83
|
+
| TODO kind | The answer |
|
|
84
|
+
| --- | --- |
|
|
85
|
+
| "some return shapes could not be inferred" | The tool already tried the dep's own declaration (a `Promise<T>` annotation, or a single-fetch body joined to the OpenAPI example — those fixtures carry an `/* inferred from … */` comment and no TODO), so this marker means BOTH usage and declaration came up empty: a computed return, a generic instantiation other than `Record<K,V>` or `Partial<T>` (both of which resolve to `{}`, a complete inhabitant), an `interface X extends Y` whose BASE does not resolve (the derived one merges its bases and bails whole if any fails), a dotted callee like `db.query.findMany`, an unannotated client factory (`getDb`), a `cache()`-wrapped export the function mapper cannot unwrap, or an annotation whose type — even one resolved through a workspace package's barrel, which works since B-8 — has a schema construct zod inference deliberately bails on (`.transform`, a union, a record, a date — `z.infer` enums and objects DO resolve since B-9, so a surviving marker means the schema left the supported subset). Give the mocked data-dep a realistic resolved value. If every consumer of the handle is itself mocked (a `getDb` whose queries are all mocked above), an opaque `{}` IS the right fixture — replace the TODO with a comment saying so. |
|
|
86
|
+
| "wrap renderTarget() in the matching Root" | The tool now does this itself when the part's OWN file exports the family (a Root-rendering sibling + a Content sibling) and the family is open-state (dialog and friends), so this TODO only appears for rootless files, cross-file families, or value-driven families (accordion/tabs). Mount the part inside its Root the way call sites do: `render(<AlertDialog><AlertDialogTrigger>Delete</AlertDialogTrigger></AlertDialog>)`, then assert its content — dialog-family parts need the content wrapper too (`<Dialog open><DialogContent>…`), and portal content is read off `baseElement`. |
|
|
87
|
+
| "server action stubbed — give it a realistic return value" | The tool now does this itself when the action returns a single literal (redirect/notFound exits discounted), so this TODO only appears when inference bailed: computed returns, two distinct shapes, or an unresolved sibling export. Read the action, pick the shape the component actually consumes, and write it into the stub. |
|
|
88
|
+
|
|
89
|
+
When you fill a fixture, also tighten the assertion: a mocked building named
|
|
90
|
+
`"flawless-name"` must actually reach the DOM
|
|
91
|
+
(`expect(screen.getByText("flawless-name")).toBeInTheDocument()`), or the spec
|
|
92
|
+
passes while rendering an empty state — the vacuous pass this tool exists to
|
|
93
|
+
prevent.
|
|
94
|
+
|
|
95
|
+
## Gated steps: the spec opens the disclosure first
|
|
96
|
+
|
|
97
|
+
A control that only exists once a disclosure is open (a button inside a dialog,
|
|
98
|
+
menu or popover) cannot be clicked by a step that never opened it. When the
|
|
99
|
+
analysis says a trigger's label is **static text arriving as a child**, the step
|
|
100
|
+
is emitted *gated*: it opens the disclosure first, clicking the spec's own child
|
|
101
|
+
span, resets the spies so the isolation asserts stay meaningful, and then queries
|
|
102
|
+
its real target with the awaiting `findBy*` form —
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
await userEvent.click(screen.getAllByText(CHILD_TEXT)[0] as HTMLElement);
|
|
106
|
+
for (const v of Object.values(handlers)) v.mockClear();
|
|
107
|
+
await userEvent.click((await screen.findAllByText("Confirm"))[0] as HTMLElement);
|
|
108
|
+
expect(handlers.onConfirm).toHaveBeenCalled();
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`CHILD_TEXT` is the `"flawless child"` string the spec renders into a component
|
|
112
|
+
that accepts children, so a children-kind gate is only emitted where the spec
|
|
113
|
+
declares it. The other gate labels come from the same ladder as any target:
|
|
114
|
+
`data-testid`, `aria-label`, literal text. Three things to know:
|
|
115
|
+
|
|
116
|
+
- It exists in the unit and browser specs. Route e2e specs do not open dialogs;
|
|
117
|
+
they assert what the route renders on its own.
|
|
118
|
+
- A disclosure the component holds open itself — a bare `open`, `defaultOpen` or
|
|
119
|
+
`open={true}` at the call site — is not gated. Nothing is closed, so there is
|
|
120
|
+
nothing to open first.
|
|
121
|
+
- **A gated step with no prelude is a bug report, not a skip.** If the label
|
|
122
|
+
could not be resolved the step is emitted unprotected and goes red on the
|
|
123
|
+
click. Do not delete the assertion and do not mark it skipped: either give the
|
|
124
|
+
trigger an accessible name the analyser can see (a `data-testid`, an
|
|
125
|
+
`aria-label`, a literal child) or teach the generator that label shape. Same
|
|
126
|
+
rule as an unlabelled canary — the red is the tool telling you where its own
|
|
127
|
+
reading stopped.
|
|
128
|
+
|
|
129
|
+
## Placement and survival
|
|
130
|
+
|
|
131
|
+
Colocated specs (`--colocate`, `<file>.flawless.test.tsx` beside the source)
|
|
132
|
+
are kept on re-runs, and their first line is a provenance stamp —
|
|
133
|
+
`// @flawless-generated sha256:<hex16> flawless-react@<version>`, hashing the
|
|
134
|
+
rest of the file. `--force` rewrites a spec only while that stamp still
|
|
135
|
+
matches; the moment you hand-harden one, the mismatch protects it (kept and
|
|
136
|
+
listed in the summary as hand-edited). Leave the stamp line alone when
|
|
137
|
+
editing: deleting it does not make the file regenerable, it just reclassifies
|
|
138
|
+
it as pre-stamp — the body edit already marks the file as yours, and the
|
|
139
|
+
delete-the-whole-file escape hatch is how you hand a spec back to the tool.
|
|
140
|
+
Specs inside the out dir are rewritten on any re-run; never hand-harden those.
|
|
141
|
+
|
|
142
|
+
Run them with `bun run flawless:unit` (the emitted vitest config selects both
|
|
143
|
+
`src/**/*.flawless.test.*` and `flawless/unit/**`). A project script that is a
|
|
144
|
+
bare `bun test` will collect colocated specs under the wrong runner — scope it
|
|
145
|
+
to `bun test tests`.
|
|
146
|
+
|
|
147
|
+
## Browser specs and the host registry
|
|
148
|
+
|
|
149
|
+
Browser specs mount through `flawless/host/` and exist only when both
|
|
150
|
+
`unit-browser` and `perf-harness` ran. The mount tables (`REGISTRY`,
|
|
151
|
+
`FN_PROPS`, `WRAP_IN`) live in `host/registry.ts`, which **regenerates on
|
|
152
|
+
every run** — a newly analysed component arrives via a plain no-force re-run,
|
|
153
|
+
nothing to hand-edit. The write-once `host/main.tsx` is only the wrapper
|
|
154
|
+
(polyfills, provider, MSW, the profiling machinery); components you register
|
|
155
|
+
by hand go in its spreads — `ALL` for the component (`const ALL = {
|
|
156
|
+
...REGISTRY, MyComponent: MyComponent as unknown as Component }` — the cast
|
|
157
|
+
is required: strict tsconfig rejects a props-typed component in a
|
|
158
|
+
Record<string, Component>), plus `ALL_FN_PROPS`/`ALL_WRAP_IN` entries when it
|
|
159
|
+
needs function-prop stubs or a wrapper chain —
|
|
160
|
+
which every lookup goes through and which survives regeneration.
|
|
161
|
+
|
|
162
|
+
`unknown component: <Name>` on a current suite means the re-run has not
|
|
163
|
+
happened yet — re-run the tool. On a pre-split suite (a `main.tsx` with no
|
|
164
|
+
`from "./registry"` import) it needs the one-time migration the run's warning
|
|
165
|
+
names: delete `host/main.tsx` and re-run (provider wiring regenerates from
|
|
166
|
+
`--provider`), or port the hand wiring into a wrapper that imports
|
|
167
|
+
`./registry`. No flag rewrites `main.tsx` — write-once is absolute, `--force`
|
|
168
|
+
included. (This was findings ledger FR-016, fixed by the registry split.)
|
|
169
|
+
|
|
170
|
+
## Never
|
|
171
|
+
|
|
172
|
+
- Never delete a reasoned `test.skip` or replace it with a hollow assertion.
|
|
173
|
+
- Never mock `cn`/`clsx` away entirely (classes vanish) or mock UI components.
|
|
174
|
+
- Never hand-harden a spec inside the out dir — colocate it first.
|
|
175
|
+
- Never delete a colocated spec's stamp line casually — that demotes the file
|
|
176
|
+
to pre-stamp; edit the body and let the stamp mismatch protect it.
|
|
177
|
+
- Never trust a green run whose fixtures are empty casts: fill, then assert
|
|
178
|
+
the fixture content reaches the DOM.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lario Borges
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|