@fougere/testing 0.6.0-alpha.0 → 0.8.0-alpha.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/README.md +1 -1
- package/dist/all.d.ts +2 -17
- package/dist/all.d.ts.map +1 -1
- package/dist/all.js +2 -17
- package/dist/all.js.map +1 -1
- package/dist/app.d.ts +4 -35
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +2 -17
- package/dist/app.js.map +1 -1
- package/dist/comparison.d.ts +3 -20
- package/dist/comparison.d.ts.map +1 -1
- package/dist/comparison.js +29 -58
- package/dist/comparison.js.map +1 -1
- package/dist/derive.d.ts +3 -9
- package/dist/derive.d.ts.map +1 -1
- package/dist/derive.js +1 -7
- package/dist/derive.js.map +1 -1
- package/dist/doors.d.ts +4 -22
- package/dist/doors.d.ts.map +1 -1
- package/dist/doors.js +6 -24
- package/dist/doors.js.map +1 -1
- package/dist/gql.d.ts +5 -24
- package/dist/gql.d.ts.map +1 -1
- package/dist/gql.js +11 -37
- package/dist/gql.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/load.d.ts +3 -20
- package/dist/load.d.ts.map +1 -1
- package/dist/load.js +6 -23
- package/dist/load.js.map +1 -1
- package/dist/remotes.d.ts +1 -6
- package/dist/remotes.d.ts.map +1 -1
- package/dist/remotes.js +1 -6
- package/dist/remotes.js.map +1 -1
- package/dist/sample.d.ts +2 -14
- package/dist/sample.d.ts.map +1 -1
- package/dist/sample.js +8 -16
- package/dist/sample.js.map +1 -1
- package/dist/scope.d.ts +5 -38
- package/dist/scope.d.ts.map +1 -1
- package/dist/scope.js +4 -26
- package/dist/scope.js.map +1 -1
- package/dist/stub.d.ts +3 -32
- package/dist/stub.d.ts.map +1 -1
- package/dist/stub.js +3 -32
- package/dist/stub.js.map +1 -1
- package/dist/sync.d.ts +2 -19
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +5 -22
- package/dist/sync.js.map +1 -1
- package/dist/vitest.d.ts +1 -20
- package/dist/vitest.d.ts.map +1 -1
- package/dist/vitest.js +1 -20
- package/dist/vitest.js.map +1 -1
- package/package.json +9 -9
- package/src/all.ts +2 -17
- package/src/app.ts +5 -41
- package/src/comparison.ts +34 -63
- package/src/derive.ts +3 -9
- package/src/doors.ts +7 -25
- package/src/gql.ts +9 -35
- package/src/index.ts +1 -1
- package/src/load.ts +7 -24
- package/src/remotes.ts +1 -6
- package/src/sample.ts +10 -30
- package/src/scope.ts +5 -38
- package/src/stub.ts +3 -32
- package/src/sync.ts +5 -22
- package/src/vitest.ts +1 -20
package/src/stub.ts
CHANGED
|
@@ -8,18 +8,7 @@ export type Port = abstract new (...args: never[]) => unknown;
|
|
|
8
8
|
/** The double handed in place of a port — one spy per method the port declares. */
|
|
9
9
|
export type Stub<T> = { [K in keyof T]: T[K] extends (...args: never[]) => unknown ? Mock : T[K] };
|
|
10
10
|
|
|
11
|
-
/**
|
|
12
|
-
* The methods a port declares, read from the prototype chain at runtime.
|
|
13
|
-
*
|
|
14
|
-
* Not written by hand and not read from the AST: `ProviderEntry` keeps `ctor`, `deps` and
|
|
15
|
-
* `filePath` and never the methods, while the prototype has them all along. So a double
|
|
16
|
-
* carries exactly what the port carries and gains a method the day the port does — which
|
|
17
|
-
* is the failure `ports.test.ts` records, a `charge is not a function` from a stand-in
|
|
18
|
-
* that did not carry what its type promised.
|
|
19
|
-
*
|
|
20
|
-
* Walks the chain because a port may itself extend one; stops at `Object.prototype`,
|
|
21
|
-
* whose members belong to no port.
|
|
22
|
-
*/
|
|
11
|
+
/** The methods a port declares, read from the prototype chain at runtime. */
|
|
23
12
|
export function methodsOf(port: Port): string[] {
|
|
24
13
|
const found = new Set<string>();
|
|
25
14
|
let proto: object | null = port.prototype as object;
|
|
@@ -34,32 +23,14 @@ export function methodsOf(port: Port): string[] {
|
|
|
34
23
|
return [...found];
|
|
35
24
|
}
|
|
36
25
|
|
|
37
|
-
/**
|
|
38
|
-
* A double for a port: every method present, every call recorded, nothing returned.
|
|
39
|
-
*
|
|
40
|
-
* What it RETURNS is not derivable and the value is the caller's to state — a service's
|
|
41
|
-
* return type is a bare TypeScript type, erased at runtime, with no declared fields for
|
|
42
|
-
* anything to build from. The same line the whole package sits on: what Fougere's
|
|
43
|
-
* vocabulary declares can be derived, arbitrary code cannot.
|
|
44
|
-
*/
|
|
26
|
+
/** A double for a port. */
|
|
45
27
|
export function stubOf<T>(port: Port): Stub<T> {
|
|
46
28
|
const double: Record<string, Mock> = {};
|
|
47
29
|
for (const method of methodsOf(port)) double[method] = vi.fn();
|
|
48
30
|
return double as Stub<T>;
|
|
49
31
|
}
|
|
50
32
|
|
|
51
|
-
/**
|
|
52
|
-
* Put doubles in front of ports, in every frond scope that answers under their name.
|
|
53
|
-
*
|
|
54
|
-
* After the boot rather than through `ports:`, because the container resolves lazily: a
|
|
55
|
-
* provider is built on first `resolve`, so a value registered before any call is the one
|
|
56
|
-
* a handler receives. `registerValue` also marks it as not the container's to dispose,
|
|
57
|
-
* which is right — the test made it.
|
|
58
|
-
*
|
|
59
|
-
* A port nobody answers under is REFUSED by name, for the reason `ports:` refuses a key
|
|
60
|
-
* that matched nothing: a double that silently stands in front of no one reads as a test
|
|
61
|
-
* that covered a case it never reached.
|
|
62
|
-
*/
|
|
33
|
+
/** Put doubles in front of ports, in every frond scope that answers under their name. */
|
|
63
34
|
export function installStubs(app: App, ports: Port[]): Map<Port, Stub<unknown>> {
|
|
64
35
|
const doubles = new Map<Port, Stub<unknown>>();
|
|
65
36
|
const scopes = app.fronds.map((frond) => app.resolve<Container>(`frond:${frond.name}`));
|
package/src/sync.ts
CHANGED
|
@@ -33,28 +33,18 @@ export async function syncedRemotes(root: string): Promise<SyncedRemote[]> {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
/**
|
|
37
|
-
* The shapes a consumer holds for one remote frond.
|
|
38
|
-
*
|
|
39
|
-
* Rebuilt from the classes rather than read back: `fougere sync` writes entities and an
|
|
40
|
-
* index and does NOT keep the card it received. So the consumer's copy of the OPERATIONS
|
|
41
|
-
* has no local counterpart at all, and only the shapes can be compared — which is the
|
|
42
|
-
* narrower half, and the one that breaks a caller silently.
|
|
43
|
-
*
|
|
44
|
-
* Closing that would mean `sync` writing the card beside the classes; it is a change to
|
|
45
|
-
* the CLI, not to this file.
|
|
46
|
-
*/
|
|
36
|
+
/** The shapes a consumer holds for one remote frond. */
|
|
47
37
|
export async function heldShapes(remote: SyncedRemote): Promise<Map<string, SchemaDescriptor>> {
|
|
48
|
-
const
|
|
38
|
+
const cards = new Map<string, SchemaDescriptor>();
|
|
49
39
|
const index = pathToFileURL(join(remote.path, 'index.ts')).href;
|
|
50
40
|
const module = await import(index) as Record<string, unknown>;
|
|
51
41
|
|
|
52
42
|
for (const [name, exported] of Object.entries(module)) {
|
|
53
43
|
const entity = exported as SchemaView | undefined;
|
|
54
44
|
if (typeof entity !== 'function' || typeof (entity as SchemaView).getFields !== 'function') continue;
|
|
55
|
-
|
|
45
|
+
cards.set(name, Card.fromSchema(entity).descriptor);
|
|
56
46
|
}
|
|
57
|
-
return
|
|
47
|
+
return cards;
|
|
58
48
|
}
|
|
59
49
|
|
|
60
50
|
/** The shapes a card announces, by the name a door carries. */
|
|
@@ -67,14 +57,7 @@ function servedShapes(card: IdentityCard, frond: string): Map<string, unknown> {
|
|
|
67
57
|
return served;
|
|
68
58
|
}
|
|
69
59
|
|
|
70
|
-
/**
|
|
71
|
-
* What the consumer's copy no longer matches.
|
|
72
|
-
*
|
|
73
|
-
* Compared by the shape's own `title` — the entity's name as the descriptor carries it —
|
|
74
|
-
* rather than by the door's, because a door is addressed in lowercase (`post`) and a class
|
|
75
|
-
* is not (`Post`), and matching those two by hand is where a name convention gets copied
|
|
76
|
-
* a third time.
|
|
77
|
-
*/
|
|
60
|
+
/** What the consumer's copy no longer matches. */
|
|
78
61
|
export function syncDriftOf(
|
|
79
62
|
held: Map<string, SchemaDescriptor>,
|
|
80
63
|
card: IdentityCard,
|
package/src/vitest.ts
CHANGED
|
@@ -1,25 +1,6 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* The vitest configuration a Fougere project needs, so nobody has to discover it.
|
|
5
|
-
*
|
|
6
|
-
* One thing to know, and it is not obvious: `graphql` guards its types with `instanceOf`,
|
|
7
|
-
* so a schema built by one loaded copy is refused by another with *"from another module
|
|
8
|
-
* or realm"*. Under vitest a module is either transformed by Vite or externalized and
|
|
9
|
-
* loaded by node, and Pothos ending up on one side while `graphql()` sits on the other
|
|
10
|
-
* makes two copies of the same file. The alias pins the path, and the inlining makes the
|
|
11
|
-
* packages that touch it take that path — neither alone is enough, which is what made
|
|
12
|
-
* this cost an afternoon.
|
|
13
|
-
*
|
|
14
|
-
* An app that serves no GraphQL pays nothing for this: the alias resolves to whatever
|
|
15
|
-
* `graphql` the project has, and no `graphql` means the option is simply unused.
|
|
16
|
-
*
|
|
17
|
-
* ```ts
|
|
18
|
-
* // vitest.config.ts
|
|
19
|
-
* import { fougereTest } from '@fougere/testing/vitest';
|
|
20
|
-
* export default fougereTest();
|
|
21
|
-
* ```
|
|
22
|
-
*/
|
|
3
|
+
/** The vitest configuration a Fougere project needs, so nobody has to discover it. */
|
|
23
4
|
export function fougereTest(overrides: Record<string, unknown> = {}): Record<string, unknown> {
|
|
24
5
|
const require = createRequire(import.meta.url);
|
|
25
6
|
let graphqlDir: string | undefined;
|