@forgeax/engine-rhi 0.0.0-dev.8d955ade1c79
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/LICENSE +202 -0
- package/README.md +234 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/async-form.test-d.d.ts +2 -0
- package/dist/__tests__/async-form.test-d.d.ts.map +1 -0
- package/dist/__tests__/binding-resource.test-d.d.ts +2 -0
- package/dist/__tests__/binding-resource.test-d.d.ts.map +1 -0
- package/dist/__tests__/buffer-mapping.test-d.d.ts +2 -0
- package/dist/__tests__/buffer-mapping.test-d.d.ts.map +1 -0
- package/dist/__tests__/canvasContext.test-d.d.ts +2 -0
- package/dist/__tests__/canvasContext.test-d.d.ts.map +1 -0
- package/dist/__tests__/caps-three-way.unit.test.d.ts +2 -0
- package/dist/__tests__/caps-three-way.unit.test.d.ts.map +1 -0
- package/dist/__tests__/caps.test-d.d.ts +2 -0
- package/dist/__tests__/caps.test-d.d.ts.map +1 -0
- package/dist/__tests__/command-encoder-spec-alignment.test-d.d.ts +2 -0
- package/dist/__tests__/command-encoder-spec-alignment.test-d.d.ts.map +1 -0
- package/dist/__tests__/createComputePipeline.test-d.d.ts +2 -0
- package/dist/__tests__/createComputePipeline.test-d.d.ts.map +1 -0
- package/dist/__tests__/createQuerySet.test-d.d.ts +2 -0
- package/dist/__tests__/createQuerySet.test-d.d.ts.map +1 -0
- package/dist/__tests__/createTextureView.test-d.d.ts +2 -0
- package/dist/__tests__/createTextureView.test-d.d.ts.map +1 -0
- package/dist/__tests__/descriptor-mirror.test-d.d.ts +2 -0
- package/dist/__tests__/descriptor-mirror.test-d.d.ts.map +1 -0
- package/dist/__tests__/draw-validation-errors.test.d.ts +2 -0
- package/dist/__tests__/draw-validation-errors.test.d.ts.map +1 -0
- package/dist/__tests__/errors-instancing.unit.test.d.ts +2 -0
- package/dist/__tests__/errors-instancing.unit.test.d.ts.map +1 -0
- package/dist/__tests__/errors.test-d.d.ts +2 -0
- package/dist/__tests__/errors.test-d.d.ts.map +1 -0
- package/dist/__tests__/limit-exceeded-detail.test-d.d.ts +2 -0
- package/dist/__tests__/limit-exceeded-detail.test-d.d.ts.map +1 -0
- package/dist/__tests__/mapped-buffer.test-d.d.ts +2 -0
- package/dist/__tests__/mapped-buffer.test-d.d.ts.map +1 -0
- package/dist/__tests__/opaque-handle.test-d.d.ts +2 -0
- package/dist/__tests__/opaque-handle.test-d.d.ts.map +1 -0
- package/dist/__tests__/owner-out-of-range-role.test.d.ts +2 -0
- package/dist/__tests__/owner-out-of-range-role.test.d.ts.map +1 -0
- package/dist/__tests__/queue-writes.test-d.d.ts +2 -0
- package/dist/__tests__/queue-writes.test-d.d.ts.map +1 -0
- package/dist/__tests__/render-pass-encoder-spec-alignment.test-d.d.ts +2 -0
- package/dist/__tests__/render-pass-encoder-spec-alignment.test-d.d.ts.map +1 -0
- package/dist/__tests__/requestAdapter.test-d.d.ts +2 -0
- package/dist/__tests__/requestAdapter.test-d.d.ts.map +1 -0
- package/dist/errors.d.ts +315 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.mjs +56 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/index.d.ts +1758 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +87 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +61 -0
- package/src/__tests__/async-form.test-d.ts +40 -0
- package/src/__tests__/binding-resource.test-d.ts +66 -0
- package/src/__tests__/buffer-mapping.test-d.ts +84 -0
- package/src/__tests__/canvasContext.test-d.ts +106 -0
- package/src/__tests__/caps-three-way.unit.test.ts +144 -0
- package/src/__tests__/caps.test-d.ts +83 -0
- package/src/__tests__/command-encoder-spec-alignment.test-d.ts +117 -0
- package/src/__tests__/createComputePipeline.test-d.ts +149 -0
- package/src/__tests__/createQuerySet.test-d.ts +81 -0
- package/src/__tests__/createTextureView.test-d.ts +127 -0
- package/src/__tests__/descriptor-mirror.test-d.ts +284 -0
- package/src/__tests__/draw-validation-errors.test.ts +155 -0
- package/src/__tests__/errors-instancing.unit.test.ts +167 -0
- package/src/__tests__/errors.test-d.ts +199 -0
- package/src/__tests__/limit-exceeded-detail.test-d.ts +33 -0
- package/src/__tests__/mapped-buffer.test-d.ts +65 -0
- package/src/__tests__/opaque-handle.test-d.ts +121 -0
- package/src/__tests__/owner-out-of-range-role.test.ts +84 -0
- package/src/__tests__/queue-writes.test-d.ts +108 -0
- package/src/__tests__/render-pass-encoder-spec-alignment.test-d.ts +202 -0
- package/src/__tests__/requestAdapter.test-d.ts +123 -0
- package/src/errors.ts +504 -0
- package/src/index.ts +2276 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// MVP-1.3 — 14 opaque handle types do not surface internal GPU fields
|
|
2
|
+
// (type-level red → green).
|
|
3
|
+
//
|
|
4
|
+
// Shape: 14 typed `Id<T>` brand-only types; user attempts to access
|
|
5
|
+
// `.gpuBuffer` / `.__brand` etc. → TypeScript compile-time red signal
|
|
6
|
+
// (auto-guarded by `@ts-expect-error`).
|
|
7
|
+
//
|
|
8
|
+
// Red expected state (at the M1 t7 commit): t10 has not declared these types
|
|
9
|
+
// → `tsc -b` reports a 'no exported member' red.
|
|
10
|
+
// Green expected state (after the M1 t10 commit): the 14 handle types are
|
|
11
|
+
// exported and brand-only → all 14 `ts-expect-error` directive blocks fire
|
|
12
|
+
// (attempts to access internal fields fail).
|
|
13
|
+
//
|
|
14
|
+
// Anchors: requirements §AC MVP-1.3 + §AI User Affordances / resource handles
|
|
15
|
+
// opaque; plan-strategy §4.3 key-test-point table row 2; §7
|
|
16
|
+
// proposition 4 / proposition 5; research §F-1 + §R5 (`__brand` is
|
|
17
|
+
// the d.ts nominal-typing workaround).
|
|
18
|
+
|
|
19
|
+
import { describe, it } from 'vitest';
|
|
20
|
+
import type {
|
|
21
|
+
BindGroup,
|
|
22
|
+
BindGroupLayout,
|
|
23
|
+
Buffer,
|
|
24
|
+
CommandBuffer,
|
|
25
|
+
CommandEncoder,
|
|
26
|
+
ComputePipeline,
|
|
27
|
+
Fence,
|
|
28
|
+
PipelineLayout,
|
|
29
|
+
QuerySet,
|
|
30
|
+
RenderPipeline,
|
|
31
|
+
Sampler,
|
|
32
|
+
ShaderModule,
|
|
33
|
+
Texture,
|
|
34
|
+
TextureView,
|
|
35
|
+
} from '../index';
|
|
36
|
+
|
|
37
|
+
describe('MVP-1.3 — 14 opaque handle types reject access to internal GPU fields', () => {
|
|
38
|
+
it('Buffer rejects access to the internal GPU object', () => {
|
|
39
|
+
const h = {} as Buffer;
|
|
40
|
+
// @ts-expect-error MVP-1.3: Buffer is opaque; gpuBuffer is not exposed.
|
|
41
|
+
h.gpuBuffer;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('Texture rejects access to the internal GPU object', () => {
|
|
45
|
+
const h = {} as Texture;
|
|
46
|
+
// @ts-expect-error MVP-1.3: Texture is opaque; gpuTexture is not exposed.
|
|
47
|
+
h.gpuTexture;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('TextureView rejects access to the internal GPU object', () => {
|
|
51
|
+
const h = {} as TextureView;
|
|
52
|
+
// @ts-expect-error MVP-1.3: TextureView is opaque; gpuTextureView is not exposed.
|
|
53
|
+
h.gpuTextureView;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('Sampler rejects access to the internal GPU object', () => {
|
|
57
|
+
const h = {} as Sampler;
|
|
58
|
+
// @ts-expect-error MVP-1.3: Sampler is opaque; gpuSampler is not exposed.
|
|
59
|
+
h.gpuSampler;
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('BindGroup rejects access to the internal GPU object', () => {
|
|
63
|
+
const h = {} as BindGroup;
|
|
64
|
+
// @ts-expect-error MVP-1.3: BindGroup is opaque; gpuBindGroup is not exposed.
|
|
65
|
+
h.gpuBindGroup;
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('BindGroupLayout rejects access to the internal GPU object', () => {
|
|
69
|
+
const h = {} as BindGroupLayout;
|
|
70
|
+
// @ts-expect-error MVP-1.3: BindGroupLayout is opaque; gpuBindGroupLayout is not exposed.
|
|
71
|
+
h.gpuBindGroupLayout;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('PipelineLayout rejects access to the internal GPU object', () => {
|
|
75
|
+
const h = {} as PipelineLayout;
|
|
76
|
+
// @ts-expect-error MVP-1.3: PipelineLayout is opaque; gpuPipelineLayout is not exposed.
|
|
77
|
+
h.gpuPipelineLayout;
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('RenderPipeline rejects access to the internal GPU object', () => {
|
|
81
|
+
const h = {} as RenderPipeline;
|
|
82
|
+
// @ts-expect-error MVP-1.3: RenderPipeline is opaque; gpuRenderPipeline is not exposed.
|
|
83
|
+
h.gpuRenderPipeline;
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('ComputePipeline rejects access to the internal GPU object', () => {
|
|
87
|
+
const h = {} as ComputePipeline;
|
|
88
|
+
// @ts-expect-error MVP-1.3: ComputePipeline is opaque; gpuComputePipeline is not exposed.
|
|
89
|
+
h.gpuComputePipeline;
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('ShaderModule rejects access to the internal GPU object', () => {
|
|
93
|
+
const h = {} as ShaderModule;
|
|
94
|
+
// @ts-expect-error MVP-1.3: ShaderModule is opaque; gpuShaderModule is not exposed.
|
|
95
|
+
h.gpuShaderModule;
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('QuerySet rejects access to the internal GPU object', () => {
|
|
99
|
+
const h = {} as QuerySet;
|
|
100
|
+
// @ts-expect-error MVP-1.3: QuerySet is opaque; gpuQuerySet is not exposed.
|
|
101
|
+
h.gpuQuerySet;
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('Fence rejects access to the internal sync object', () => {
|
|
105
|
+
const h = {} as Fence;
|
|
106
|
+
// @ts-expect-error MVP-1.3: Fence is opaque; internal sync handle is not exposed.
|
|
107
|
+
h.gpuFence;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('CommandEncoder rejects access to the internal GPU object', () => {
|
|
111
|
+
const h = {} as CommandEncoder;
|
|
112
|
+
// @ts-expect-error MVP-1.3: CommandEncoder is opaque; gpuCommandEncoder is not exposed.
|
|
113
|
+
h.gpuCommandEncoder;
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('CommandBuffer rejects access to the internal GPU object', () => {
|
|
117
|
+
const h = {} as CommandBuffer;
|
|
118
|
+
// @ts-expect-error MVP-1.3: CommandBuffer is opaque; gpuCommandBuffer is not exposed.
|
|
119
|
+
h.gpuCommandBuffer;
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// feat-20260709-editor-world-partition-editorworld-super-composite / M1 / w3
|
|
2
|
+
// (RED — impl lands in w6/w7). Contract test for the `role` field added to the
|
|
3
|
+
// `render-system-owner-out-of-range` structured error detail.
|
|
4
|
+
//
|
|
5
|
+
// Today the draw entry validates a SINGLE `owner` index and, on out-of-range,
|
|
6
|
+
// returns `RhiError { code: 'render-system-owner-out-of-range', detail: {
|
|
7
|
+
// owner, worldCount } }` (errors.ts validateDrawArgs). This feature splits the
|
|
8
|
+
// single owner into `cameraOwner` + `resourceOwner`, so a validation failure
|
|
9
|
+
// must say WHICH index was out of range. w7 extends the detail to
|
|
10
|
+
// `{ role: 'camera' | 'resource', owner, worldCount }`; w6 threads two indices
|
|
11
|
+
// through the draw entry and validates cameraOwner BEFORE resourceOwner.
|
|
12
|
+
//
|
|
13
|
+
// This test pins the future contract as the SSOT for w6/w7. The rhi package is
|
|
14
|
+
// World-free (it owns the RhiErrorCode closed union, architecture-principles §1
|
|
15
|
+
// SSOT) and cannot import the runtime Renderer — so, exactly like the existing
|
|
16
|
+
// draw-validation-errors.test.ts, the contract is exercised through the pure
|
|
17
|
+
// `validateDrawArgs` primitive rather than through `renderer.draw` itself. The
|
|
18
|
+
// future primitive shape is `validateDrawArgs(worldCount, { cameraOwner,
|
|
19
|
+
// resourceOwner })`. Until w6/w7 evolve the signature + detail, these calls are
|
|
20
|
+
// a type/shape mismatch and this file is RED (test-first).
|
|
21
|
+
//
|
|
22
|
+
// No new error code is added (D-3: 0 new error codes) — only `.detail` grows a
|
|
23
|
+
// `role` discriminator.
|
|
24
|
+
//
|
|
25
|
+
// Anchors:
|
|
26
|
+
// requirements AC-08 (out-of-range/missing returns a structured error aligned
|
|
27
|
+
// with the existing `render-system-owner-out-of-range` + `.detail`)
|
|
28
|
+
// plan-strategy §3.3 interface example (err.detail === { role: 'resource',
|
|
29
|
+
// owner: 3, worldCount: 2 }) + §2 D-3
|
|
30
|
+
// research F2.3 (existing .detail = { owner, worldCount }; add role field)
|
|
31
|
+
|
|
32
|
+
import { describe, expect, it } from 'vitest';
|
|
33
|
+
import { validateDrawArgs } from '../errors';
|
|
34
|
+
|
|
35
|
+
// The role-carrying detail w7 will attach to the existing code. Declared
|
|
36
|
+
// locally so this test states the future contract independent of the
|
|
37
|
+
// (not-yet-updated) RhiOwnerOutOfRangeDetail export.
|
|
38
|
+
interface OwnerOutOfRangeRoleDetail {
|
|
39
|
+
readonly role: 'camera' | 'resource';
|
|
40
|
+
readonly owner: number;
|
|
41
|
+
readonly worldCount: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
describe('owner-out-of-range role detail (w3, AC-08)', () => {
|
|
45
|
+
it("cameraOwner out of range -> code render-system-owner-out-of-range, detail.role === 'camera'", () => {
|
|
46
|
+
// worldCount=2, cameraOwner=99 is out of range; resourceOwner=0 is valid.
|
|
47
|
+
const r = validateDrawArgs(2, { cameraOwner: 99, resourceOwner: 0 });
|
|
48
|
+
expect(r.ok).toBe(false);
|
|
49
|
+
if (!r.ok) {
|
|
50
|
+
expect(r.error.code).toBe('render-system-owner-out-of-range');
|
|
51
|
+
const d = r.error.detail as OwnerOutOfRangeRoleDetail;
|
|
52
|
+
expect(d.role).toBe('camera');
|
|
53
|
+
expect(d.owner).toBe(99);
|
|
54
|
+
expect(d.worldCount).toBe(2);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("resourceOwner out of range -> code render-system-owner-out-of-range, detail.role === 'resource'", () => {
|
|
59
|
+
// worldCount=2, cameraOwner=0 is valid; resourceOwner=99 is out of range.
|
|
60
|
+
const r = validateDrawArgs(2, { cameraOwner: 0, resourceOwner: 99 });
|
|
61
|
+
expect(r.ok).toBe(false);
|
|
62
|
+
if (!r.ok) {
|
|
63
|
+
expect(r.error.code).toBe('render-system-owner-out-of-range');
|
|
64
|
+
const d = r.error.detail as OwnerOutOfRangeRoleDetail;
|
|
65
|
+
expect(d.role).toBe('resource');
|
|
66
|
+
expect(d.owner).toBe(99);
|
|
67
|
+
expect(d.worldCount).toBe(2);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("both indices out of range -> reports the FIRST offender (camera), detail.role === 'camera'", () => {
|
|
72
|
+
// Both out of range. D-3 / w6 orders the check cameraOwner-before-resource,
|
|
73
|
+
// so the reported role is 'camera' (the first offender) with its own index.
|
|
74
|
+
const r = validateDrawArgs(2, { cameraOwner: 7, resourceOwner: 8 });
|
|
75
|
+
expect(r.ok).toBe(false);
|
|
76
|
+
if (!r.ok) {
|
|
77
|
+
expect(r.error.code).toBe('render-system-owner-out-of-range');
|
|
78
|
+
const d = r.error.detail as OwnerOutOfRangeRoleDetail;
|
|
79
|
+
expect(d.role).toBe('camera');
|
|
80
|
+
expect(d.owner).toBe(7);
|
|
81
|
+
expect(d.worldCount).toBe(2);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// AC-08 (Q5) — RhiQueue.writeTexture + copyExternalImageToTexture Pick<spec>
|
|
2
|
+
// narrow type-layer assertions.
|
|
3
|
+
//
|
|
4
|
+
// D-P5 (plan-strategy §spec-rename) — the spec evolved field-info wrappers;
|
|
5
|
+
// pre-rename names (authored Jan 2024, retired Apr 2026) are no longer
|
|
6
|
+
// present in @webgpu/types 0.1.69 and the rhi interface now uses the
|
|
7
|
+
// authoritative new names (GPUCopyExternalImageSourceInfo +
|
|
8
|
+
// GPUCopyExternalImageDestInfo per W3C WebGPU CR Apr 2026); any regression
|
|
9
|
+
// would surface as TS2304 at type lookup. Historical rename context lives
|
|
10
|
+
// in .forgeax-harness/forgeax-loop/feat-20260511-asset-system-v1/research.md
|
|
11
|
+
// D-P5 + requirements research Finding 1(a) + Finding 3(a); the pre-rename
|
|
12
|
+
// names are deliberately NOT quoted here so the `GPUImg*` grep gate
|
|
13
|
+
// (feat-20260511-asset-system-v1 / w32) returns 0 hits repo-wide.
|
|
14
|
+
//
|
|
15
|
+
// w17 contract:
|
|
16
|
+
// (a) writeTexture(destination, data, dataLayout, size) where:
|
|
17
|
+
// destination is Pick<GPUTexelCopyTextureInfo, ...> (NOT verbatim spec);
|
|
18
|
+
// dataLayout is Pick<GPUTexelCopyBufferLayout, ...>.
|
|
19
|
+
// (b) copyExternalImageToTexture(source, destination, copySize) where:
|
|
20
|
+
// source is Pick<GPUCopyExternalImageSourceInfo, ...> (spec rename);
|
|
21
|
+
// destination is the opaque-handle ExternalImageTextureDestination.
|
|
22
|
+
// (c) r12-lint Pick<GPU*Descriptor count >= 20 follow-on (verified by w22
|
|
23
|
+
// gate, not this test-d).
|
|
24
|
+
//
|
|
25
|
+
// Charter: proposition 4 (explicit failure - excess fields trip TS2375 at the
|
|
26
|
+
// call site) + proposition 5 (consistent abstraction - dual backends mirror
|
|
27
|
+
// the same Pick<> narrowing). Anchors: requirements AC-08; research R-04
|
|
28
|
+
// §4.1-§4.3; plan-strategy D-P5 spec rename critical fact.
|
|
29
|
+
|
|
30
|
+
/// <reference types="@webgpu/types" />
|
|
31
|
+
|
|
32
|
+
import { describe, expectTypeOf, it } from 'vitest';
|
|
33
|
+
import type {
|
|
34
|
+
Buffer,
|
|
35
|
+
ExternalImageTextureDestination,
|
|
36
|
+
Result,
|
|
37
|
+
RhiError,
|
|
38
|
+
RhiQueue,
|
|
39
|
+
Texture,
|
|
40
|
+
TextureView,
|
|
41
|
+
TextureWriteDestination,
|
|
42
|
+
} from '../index';
|
|
43
|
+
|
|
44
|
+
/** Strict structural equality helper. */
|
|
45
|
+
type Equals<X, Y> =
|
|
46
|
+
(<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false;
|
|
47
|
+
|
|
48
|
+
describe('AC-08 — RhiQueue.writeTexture Pick<spec> narrow', () => {
|
|
49
|
+
it('writeTexture method exists on RhiQueue', () => {
|
|
50
|
+
type Method = RhiQueue['writeTexture'];
|
|
51
|
+
expectTypeOf<Method>().toBeFunction();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('writeTexture destination keeps the opaque Texture resource boundary', () => {
|
|
55
|
+
type DestParam = Parameters<RhiQueue['writeTexture']>[0];
|
|
56
|
+
type ExpectedShape = TextureWriteDestination;
|
|
57
|
+
// Strict equality — the descriptor has exactly the four spec fields, but
|
|
58
|
+
// its resource is the RHI-opaque Texture rather than GPUTexture.
|
|
59
|
+
type IsExact = Equals<DestParam, ExpectedShape>;
|
|
60
|
+
expectTypeOf<IsExact>().toEqualTypeOf<true>();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('writeTexture dataLayout param strictly equals Pick<GPUTexelCopyBufferLayout, ...>', () => {
|
|
64
|
+
type LayoutParam = Parameters<RhiQueue['writeTexture']>[2];
|
|
65
|
+
type ExpectedShape = Pick<GPUTexelCopyBufferLayout, 'offset' | 'bytesPerRow' | 'rowsPerImage'>;
|
|
66
|
+
type IsExact = Equals<LayoutParam, ExpectedShape>;
|
|
67
|
+
expectTypeOf<IsExact>().toEqualTypeOf<true>();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('writeTexture returns Result<void, RhiError>', () => {
|
|
71
|
+
type Ret = ReturnType<RhiQueue['writeTexture']>;
|
|
72
|
+
type IsResult = Equals<Ret, Result<void, RhiError>>;
|
|
73
|
+
expectTypeOf<IsResult>().toEqualTypeOf<true>();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('AC-08 — RhiQueue.copyExternalImageToTexture Pick<spec> narrow + spec rename', () => {
|
|
78
|
+
it('copyExternalImageToTexture method exists on RhiQueue', () => {
|
|
79
|
+
type Method = RhiQueue['copyExternalImageToTexture'];
|
|
80
|
+
expectTypeOf<Method>().toBeFunction();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('source param strictly equals Pick<GPUCopyExternalImageSourceInfo, ...> (spec rename D-P5)', () => {
|
|
84
|
+
type SourceParam = Parameters<RhiQueue['copyExternalImageToTexture']>[0];
|
|
85
|
+
type ExpectedShape = Pick<GPUCopyExternalImageSourceInfo, 'source' | 'origin' | 'flipY'>;
|
|
86
|
+
type IsExact = Equals<SourceParam, ExpectedShape>;
|
|
87
|
+
expectTypeOf<IsExact>().toEqualTypeOf<true>();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('destination param keeps the RHI Texture opaque (D-P5)', () => {
|
|
91
|
+
type DestParam = Parameters<RhiQueue['copyExternalImageToTexture']>[1];
|
|
92
|
+
type IsExact = Equals<DestParam, ExternalImageTextureDestination>;
|
|
93
|
+
expectTypeOf<IsExact>().toEqualTypeOf<true>();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('copyExternalImageToTexture returns Result<void, RhiError>', () => {
|
|
97
|
+
type Ret = ReturnType<RhiQueue['copyExternalImageToTexture']>;
|
|
98
|
+
type IsResult = Equals<Ret, Result<void, RhiError>>;
|
|
99
|
+
expectTypeOf<IsResult>().toEqualTypeOf<true>();
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Sanity peg — keep Buffer / Texture / TextureView in scope so they don't get
|
|
104
|
+
// stripped by tree-shake import elision (the rhi entry rolls these up so
|
|
105
|
+
// downstream consumers can grep on the forgeax handle names).
|
|
106
|
+
type _Peg = { b: Buffer; t: Texture; tv: TextureView };
|
|
107
|
+
declare const _peg: _Peg;
|
|
108
|
+
void _peg;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// w4 type-level - RhiRenderPassEncoder spec method-set alignment (research F-2 /
|
|
2
|
+
// plan-strategy D-S4: 17 spec stable + 1 setBindGroup overload + 3 placeholder).
|
|
3
|
+
//
|
|
4
|
+
// Asserts (RED until w5 lands the impl):
|
|
5
|
+
// - 17 spec stable methods exist on RhiRenderPassEncoder
|
|
6
|
+
// - setBindGroup has both overloads (a) array form (b) Uint32Array form
|
|
7
|
+
// - 3 placeholder methods (executeBundles / beginOcclusionQuery /
|
|
8
|
+
// endOcclusionQuery) are present in the interface and return
|
|
9
|
+
// Result<void, RhiError> (so AI users can route 'rhi-not-available' at
|
|
10
|
+
// runtime; charter proposition 4 explicit failure).
|
|
11
|
+
// - setImmediates (PROPOSED) is NOT exposed (per D-S4 explicit non-receipt;
|
|
12
|
+
// charter proposition 4: untested features hide behind caps, not surfaces).
|
|
13
|
+
//
|
|
14
|
+
// Method NAMES align byte-for-byte with `@webgpu/types`
|
|
15
|
+
// GPURenderPassEncoder + GPURenderCommandsMixin + GPUBindingCommandsMixin +
|
|
16
|
+
// GPUDebugCommandsMixin (research F-2).
|
|
17
|
+
//
|
|
18
|
+
// Charter mapping: proposition 1 (progressive disclosure: AI sees the full
|
|
19
|
+
// spec surface in one read) + proposition 5 (consistent abstraction: spec
|
|
20
|
+
// stable methods land here regardless of capability).
|
|
21
|
+
|
|
22
|
+
import { describe, expectTypeOf, it } from 'vitest';
|
|
23
|
+
import type {
|
|
24
|
+
BindGroup,
|
|
25
|
+
Buffer,
|
|
26
|
+
RenderPassColorAttachment,
|
|
27
|
+
RenderPassDepthStencilAttachment,
|
|
28
|
+
Result,
|
|
29
|
+
RhiError,
|
|
30
|
+
RhiRenderPassEncoder,
|
|
31
|
+
Texture,
|
|
32
|
+
TextureView,
|
|
33
|
+
} from '../index';
|
|
34
|
+
|
|
35
|
+
describe('w4 - RhiRenderPassEncoder spec method set (17 stable + 1 overload + 3 placeholders)', () => {
|
|
36
|
+
// 7 already-shipped methods (Round 1 baseline; locked in this closure):
|
|
37
|
+
it('keeps existing setPipeline / setIndexBuffer / setVertexBuffer', () => {
|
|
38
|
+
expectTypeOf<RhiRenderPassEncoder['setPipeline']>().toBeFunction();
|
|
39
|
+
expectTypeOf<RhiRenderPassEncoder['setIndexBuffer']>().toBeFunction();
|
|
40
|
+
expectTypeOf<RhiRenderPassEncoder['setVertexBuffer']>().toBeFunction();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('keeps existing draw / drawIndexed / end', () => {
|
|
44
|
+
expectTypeOf<RhiRenderPassEncoder['draw']>().toBeFunction();
|
|
45
|
+
expectTypeOf<RhiRenderPassEncoder['drawIndexed']>().toBeFunction();
|
|
46
|
+
expectTypeOf<RhiRenderPassEncoder['end']>().toBeFunction();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// 10 new spec stable methods (research F-2; D-S4 IN list):
|
|
50
|
+
it('exposes setViewport(x, y, w, h, minDepth, maxDepth)', () => {
|
|
51
|
+
expectTypeOf<RhiRenderPassEncoder['setViewport']>().toBeFunction();
|
|
52
|
+
type Expected = (
|
|
53
|
+
x: number,
|
|
54
|
+
y: number,
|
|
55
|
+
w: number,
|
|
56
|
+
h: number,
|
|
57
|
+
minDepth: number,
|
|
58
|
+
maxDepth: number,
|
|
59
|
+
) => void;
|
|
60
|
+
expectTypeOf<Expected>().toMatchTypeOf<RhiRenderPassEncoder['setViewport']>();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('exposes setScissorRect(x, y, w, h)', () => {
|
|
64
|
+
expectTypeOf<RhiRenderPassEncoder['setScissorRect']>().toBeFunction();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('exposes setBlendConstant(color)', () => {
|
|
68
|
+
expectTypeOf<RhiRenderPassEncoder['setBlendConstant']>().toBeFunction();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('exposes setStencilReference(reference)', () => {
|
|
72
|
+
expectTypeOf<RhiRenderPassEncoder['setStencilReference']>().toBeFunction();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('exposes drawIndirect(buffer, offset)', () => {
|
|
76
|
+
expectTypeOf<RhiRenderPassEncoder['drawIndirect']>().toBeFunction();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('exposes drawIndexedIndirect(buffer, offset)', () => {
|
|
80
|
+
expectTypeOf<RhiRenderPassEncoder['drawIndexedIndirect']>().toBeFunction();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('exposes pushDebugGroup / popDebugGroup / insertDebugMarker (DebugCommandsMixin)', () => {
|
|
84
|
+
expectTypeOf<RhiRenderPassEncoder['pushDebugGroup']>().toBeFunction();
|
|
85
|
+
expectTypeOf<RhiRenderPassEncoder['popDebugGroup']>().toBeFunction();
|
|
86
|
+
expectTypeOf<RhiRenderPassEncoder['insertDebugMarker']>().toBeFunction();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// setBindGroup has 2 overloads per spec:
|
|
90
|
+
it('setBindGroup overload (a): array form (existing)', () => {
|
|
91
|
+
type ArrayForm = (
|
|
92
|
+
index: number,
|
|
93
|
+
bindGroup: BindGroup,
|
|
94
|
+
dynamicOffsets?: readonly number[] | undefined,
|
|
95
|
+
) => void;
|
|
96
|
+
type HasArrayOverload = RhiRenderPassEncoder['setBindGroup'] extends ArrayForm ? true : false;
|
|
97
|
+
expectTypeOf<HasArrayOverload>().toEqualTypeOf<true>();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('setBindGroup overload (b): Uint32Array slice form (new in this closure)', () => {
|
|
101
|
+
type SliceForm = (
|
|
102
|
+
index: number,
|
|
103
|
+
bindGroup: BindGroup,
|
|
104
|
+
dynamicOffsetsData: Uint32Array,
|
|
105
|
+
dynamicOffsetsDataStart: number,
|
|
106
|
+
dynamicOffsetsDataLength: number,
|
|
107
|
+
) => void;
|
|
108
|
+
type HasSliceOverload = RhiRenderPassEncoder['setBindGroup'] extends SliceForm ? true : false;
|
|
109
|
+
expectTypeOf<HasSliceOverload>().toEqualTypeOf<true>();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// 3 placeholders: signature lives in the interface; shim returns Result.err
|
|
113
|
+
// ({ code: 'rhi-not-available' }) at runtime per D-S4.
|
|
114
|
+
it('exposes executeBundles (placeholder; capability-gated by RenderBundle)', () => {
|
|
115
|
+
type Method = RhiRenderPassEncoder['executeBundles'];
|
|
116
|
+
expectTypeOf<Method>().toBeFunction();
|
|
117
|
+
expectTypeOf<Method>().returns.toEqualTypeOf<Result<void, RhiError>>();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('exposes beginOcclusionQuery (placeholder; capability-gated by QuerySet)', () => {
|
|
121
|
+
type Method = RhiRenderPassEncoder['beginOcclusionQuery'];
|
|
122
|
+
expectTypeOf<Method>().toBeFunction();
|
|
123
|
+
expectTypeOf<Method>().returns.toEqualTypeOf<Result<void, RhiError>>();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('exposes endOcclusionQuery (placeholder; capability-gated by QuerySet)', () => {
|
|
127
|
+
type Method = RhiRenderPassEncoder['endOcclusionQuery'];
|
|
128
|
+
expectTypeOf<Method>().toBeFunction();
|
|
129
|
+
expectTypeOf<Method>().returns.toEqualTypeOf<Result<void, RhiError>>();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('does NOT expose setImmediates (PROPOSED; D-S4 explicit non-receipt)', () => {
|
|
133
|
+
// Type-level guard: the field must NOT exist on the interface so that
|
|
134
|
+
// AI users see a tsc red signal if they attempt to call it.
|
|
135
|
+
type HasSetImmediates = 'setImmediates' extends keyof RhiRenderPassEncoder ? true : false;
|
|
136
|
+
expectTypeOf<HasSetImmediates>().toEqualTypeOf<false>();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Buffer reference smoke (sanity: arg types use forgeax opaque handles).
|
|
140
|
+
it('drawIndirect uses forgeax Buffer (opaque handle, not raw GPUBuffer)', () => {
|
|
141
|
+
type Method = RhiRenderPassEncoder['drawIndirect'];
|
|
142
|
+
type Expected = (indirectBuffer: Buffer, indirectOffset: number) => void;
|
|
143
|
+
expectTypeOf<Expected>().toMatchTypeOf<Method>();
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// w14 - view narrow Path X breakage point #1 (requirements IN-2 / AC-02 /
|
|
148
|
+
// research 8.2): the 4 view fields move from `Texture` (D-S5 temporary
|
|
149
|
+
// tightening) to `TextureView` (the spec-aligned target after M1 shipped
|
|
150
|
+
// `device.createTextureView`).
|
|
151
|
+
//
|
|
152
|
+
// Field count breakdown (research 8.2 IMPORTANT box):
|
|
153
|
+
// - 1 direct: RenderPassColorAttachment.view
|
|
154
|
+
// - 1 direct: RenderPassColorAttachment.resolveTarget?
|
|
155
|
+
// - 1 direct: RenderPassDepthStencilAttachment.view
|
|
156
|
+
// - 1 indirect: RenderPassDescriptor.colorAttachments[N].view propagates
|
|
157
|
+
// through the element type of RenderPassColorAttachment (covered by the
|
|
158
|
+
// direct assertion on RenderPassColorAttachment.view).
|
|
159
|
+
//
|
|
160
|
+
// Red expected state: this block fails tsc -b before w15 lands the narrow
|
|
161
|
+
// (current types are `Texture`; `expectAssignable<TextureView>` fails because
|
|
162
|
+
// Texture and TextureView are disjoint brand types).
|
|
163
|
+
// Green expected state (after w15 commit): all expectTypeOf assertions pass.
|
|
164
|
+
//
|
|
165
|
+
// Charter mapping: proposition 4 (explicit failure: AI users get a tsc red
|
|
166
|
+
// signal at the call site instead of a runtime swap) + proposition 5
|
|
167
|
+
// (consistent abstraction: the field type matches what `createTextureView`
|
|
168
|
+
// returns).
|
|
169
|
+
describe('w14 - view narrow Path X (breakage point #1): RenderPassColorAttachment.view / .resolveTarget / RenderPassDepthStencilAttachment.view = TextureView', () => {
|
|
170
|
+
it('RenderPassColorAttachment.view is TextureView (not Texture)', () => {
|
|
171
|
+
type ViewField = RenderPassColorAttachment['view'];
|
|
172
|
+
expectTypeOf<ViewField>().toEqualTypeOf<TextureView>();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('RenderPassColorAttachment.view rejects Texture brand', () => {
|
|
176
|
+
type ViewField = RenderPassColorAttachment['view'];
|
|
177
|
+
type TextureAssignsToView = Texture extends ViewField ? true : false;
|
|
178
|
+
expectTypeOf<TextureAssignsToView>().toEqualTypeOf<false>();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('RenderPassColorAttachment.resolveTarget is TextureView | undefined (not Texture | undefined)', () => {
|
|
182
|
+
type ResolveTargetField = RenderPassColorAttachment['resolveTarget'];
|
|
183
|
+
expectTypeOf<ResolveTargetField>().toEqualTypeOf<TextureView | undefined>();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('RenderPassColorAttachment.resolveTarget rejects Texture brand', () => {
|
|
187
|
+
type ResolveTargetField = NonNullable<RenderPassColorAttachment['resolveTarget']>;
|
|
188
|
+
type TextureAssignsToResolveTarget = Texture extends ResolveTargetField ? true : false;
|
|
189
|
+
expectTypeOf<TextureAssignsToResolveTarget>().toEqualTypeOf<false>();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('RenderPassDepthStencilAttachment.view is TextureView (not Texture)', () => {
|
|
193
|
+
type ViewField = RenderPassDepthStencilAttachment['view'];
|
|
194
|
+
expectTypeOf<ViewField>().toEqualTypeOf<TextureView>();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('RenderPassDepthStencilAttachment.view rejects Texture brand', () => {
|
|
198
|
+
type ViewField = RenderPassDepthStencilAttachment['view'];
|
|
199
|
+
type TextureAssignsToView = Texture extends ViewField ? true : false;
|
|
200
|
+
expectTypeOf<TextureAssignsToView>().toEqualTypeOf<false>();
|
|
201
|
+
});
|
|
202
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// w17 - RhiInstance + RhiAdapter type-level contract test (TDD red).
|
|
2
|
+
//
|
|
3
|
+
// Locks M3 break-point #2 surface (plan-tasks w17 / requirements IN-4 / AC-04;
|
|
4
|
+
// plan-strategy K-5 + K-6):
|
|
5
|
+
// RhiInstance.requestAdapter(opts?): Promise<Result<RhiAdapter, RhiError>>
|
|
6
|
+
// RhiAdapter.features: ReadonlySet<GPUFeatureName> (Round 3 fix-up F-P1-2:
|
|
7
|
+
// was ReadonlyArray<string>; aligned with RhiDevice.features for cross-
|
|
8
|
+
// tier shape uniformity — AI users use `.has(name)` on both layers)
|
|
9
|
+
// RhiAdapter.limits: Readonly<Record<string, number>>
|
|
10
|
+
// RhiAdapter.requestDevice(opts?): Promise<Result<RhiDevice, RhiError>>
|
|
11
|
+
//
|
|
12
|
+
// Strict two-step path mirrors wgpu / Dawn source (research §6); break-point
|
|
13
|
+
// #2 deprecates the top-level `rhi.requestDevice(opts)` factory in favour of
|
|
14
|
+
// `rhi.requestAdapter() -> adapter.requestDevice(opts)`.
|
|
15
|
+
//
|
|
16
|
+
// Red expected: tsc -b fails with TS2305 / TS2339 (missing RhiInstance /
|
|
17
|
+
// RhiAdapter / RequestAdapterOptions / RequestDeviceOptions). Turns green
|
|
18
|
+
// after w18 ships.
|
|
19
|
+
//
|
|
20
|
+
// F-1 ai-user-review absorption: readonly REVERSE assertions ensure mutation
|
|
21
|
+
// of `features[0]` / `limits['x']` is rejected at compile time (charter
|
|
22
|
+
// proposition 4 explicit failure + proposition 5 consistent abstraction).
|
|
23
|
+
//
|
|
24
|
+
// Anchors: requirements §IN-4 / §AC-04 / break-point #2; research §6.1 + §6.2
|
|
25
|
+
// + §6.3 wgpu / Dawn strict two-step source; plan-strategy §2 K-5 +
|
|
26
|
+
// K-6 + §6 M3 + K-10.
|
|
27
|
+
|
|
28
|
+
import { describe, expectTypeOf, it } from 'vitest';
|
|
29
|
+
import type {
|
|
30
|
+
RequestAdapterOptions,
|
|
31
|
+
RequestDeviceOptions,
|
|
32
|
+
Result,
|
|
33
|
+
RhiAdapter,
|
|
34
|
+
RhiDevice,
|
|
35
|
+
RhiError,
|
|
36
|
+
RhiInstance,
|
|
37
|
+
} from '../index';
|
|
38
|
+
|
|
39
|
+
describe('w17 type-level - RhiInstance.requestAdapter signature (K-5 / break-point #2)', () => {
|
|
40
|
+
it('returns Promise<Result<RhiAdapter, RhiError>>', () => {
|
|
41
|
+
type Sig = RhiInstance['requestAdapter'];
|
|
42
|
+
type Ret = ReturnType<Sig>;
|
|
43
|
+
expectTypeOf<Ret>().toEqualTypeOf<Promise<Result<RhiAdapter, RhiError>>>();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('takes optional RequestAdapterOptions + compatibleSurface parameters', () => {
|
|
47
|
+
type Sig = RhiInstance['requestAdapter'];
|
|
48
|
+
type Params = Parameters<Sig>;
|
|
49
|
+
expectTypeOf<Params>().toEqualTypeOf<
|
|
50
|
+
[
|
|
51
|
+
opts?: RequestAdapterOptions | undefined,
|
|
52
|
+
compatibleSurface?: HTMLCanvasElement | OffscreenCanvas | undefined,
|
|
53
|
+
]
|
|
54
|
+
>();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('w17 type-level - RhiAdapter.features is ReadonlySet<GPUFeatureName> (K-5 / F-P1-2)', () => {
|
|
59
|
+
it('features field type is ReadonlySet<GPUFeatureName>', () => {
|
|
60
|
+
expectTypeOf<RhiAdapter['features']>().toEqualTypeOf<ReadonlySet<GPUFeatureName>>();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('mutating features.add(...) is rejected at compile time (F-1 readonly REVERSE)', () => {
|
|
64
|
+
const adapter = {} as RhiAdapter;
|
|
65
|
+
// @ts-expect-error F-1 ai-user-review: ReadonlySet rejects .add()
|
|
66
|
+
adapter.features.add('timestamp-query');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('mutating features.delete(...) is rejected at compile time (F-1)', () => {
|
|
70
|
+
const adapter = {} as RhiAdapter;
|
|
71
|
+
// @ts-expect-error F-1 ai-user-review: ReadonlySet rejects .delete()
|
|
72
|
+
adapter.features.delete('timestamp-query');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('mutating features.clear() is rejected at compile time (F-1)', () => {
|
|
76
|
+
const adapter = {} as RhiAdapter;
|
|
77
|
+
// @ts-expect-error F-1 ai-user-review: ReadonlySet rejects .clear()
|
|
78
|
+
adapter.features.clear();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('reassigning features whole set is rejected at compile time (F-1)', () => {
|
|
82
|
+
const adapter = {} as RhiAdapter;
|
|
83
|
+
// @ts-expect-error features is a readonly property
|
|
84
|
+
adapter.features = new Set();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('.has(name) is the supported read API (cross-tier uniform with RhiDevice.features)', () => {
|
|
88
|
+
const adapter = {} as RhiAdapter;
|
|
89
|
+
expectTypeOf(adapter.features.has('timestamp-query')).toEqualTypeOf<boolean>();
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('w17 type-level - RhiAdapter.limits is Readonly<Record<string, number>> (K-5)', () => {
|
|
94
|
+
it('limits field type is Readonly<Record<string, number>>', () => {
|
|
95
|
+
expectTypeOf<RhiAdapter['limits']>().toEqualTypeOf<Readonly<Record<string, number>>>();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('mutating limits["x"] is rejected at compile time (F-1 readonly REVERSE)', () => {
|
|
99
|
+
const adapter = {} as RhiAdapter;
|
|
100
|
+
// @ts-expect-error F-1 ai-user-review: limits['x'] write is rejected by TS
|
|
101
|
+
adapter.limits.x = 0;
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('reassigning limits whole record is rejected at compile time (F-1)', () => {
|
|
105
|
+
const adapter = {} as RhiAdapter;
|
|
106
|
+
// @ts-expect-error limits is a readonly property
|
|
107
|
+
adapter.limits = { x: 0 };
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('w17 type-level - RhiAdapter.requestDevice signature (K-6)', () => {
|
|
112
|
+
it('returns Promise<Result<RhiDevice, RhiError>> (NOT a tuple)', () => {
|
|
113
|
+
type Sig = RhiAdapter['requestDevice'];
|
|
114
|
+
type Ret = ReturnType<Sig>;
|
|
115
|
+
expectTypeOf<Ret>().toEqualTypeOf<Promise<Result<RhiDevice, RhiError>>>();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('takes an optional RequestDeviceOptions parameter', () => {
|
|
119
|
+
type Sig = RhiAdapter['requestDevice'];
|
|
120
|
+
type Params = Parameters<Sig>;
|
|
121
|
+
expectTypeOf<Params>().toEqualTypeOf<[opts?: RequestDeviceOptions | undefined]>();
|
|
122
|
+
});
|
|
123
|
+
});
|