Skip to content

Commit 8b8c6f2

Browse files
committed
test(hooks): type-level pins for overload deprecation resolution
A dedicated typetest file asserts which calls resolve to @deprecated overloads, in both directions: calls that must be deprecated carry a no-deprecated disable, and eslint runs typetest files with reportUnusedDisableDirectives: 'error' — so if the resolution ever changes, the now-unused directive fails lint; calls that must stay non-deprecated are written bare and fail no-deprecated directly on regression. @ts-expect-error pins the invalid param combinations, and return-type annotations pin the required-narrowing behavior. Gated by yarn lint + yarn tsc (the jest body is a vessel; jest matches all of __tests__).
1 parent 80912d0 commit 8b8c6f2

2 files changed

Lines changed: 118 additions & 0 deletions

File tree

eslint.config.mjs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,15 @@ export default defineConfig([
4949
'@typescript-eslint/no-deprecated': 'error',
5050
},
5151
},
52+
{
53+
// Type-test files pin overload/deprecation resolution: a
54+
// no-deprecated disable there asserts "this call IS deprecated", so an
55+
// unused directive means the resolution regressed and must fail lint.
56+
files: ['**/*.typetest.{ts,tsx}'],
57+
linterOptions: {
58+
reportUnusedDisableDirectives: 'error',
59+
},
60+
},
5261
{
5362
ignores: [
5463
'node_modules/',
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/**
2+
* Type-level pins for useViewModelInstance's overload resolution.
3+
*
4+
* This file is a lint/compile target, not a runtime suite: eslint runs it
5+
* with `reportUnusedDisableDirectives: 'error'` (see eslint.config.mjs), so
6+
* - calls that MUST resolve to a @deprecated overload carry a
7+
* `eslint-disable-next-line @typescript-eslint/no-deprecated` — if the
8+
* resolution ever changes, the directive becomes unused and lint fails;
9+
* - calls that MUST stay non-deprecated are written bare — if they ever
10+
* resolve to a deprecated overload, `no-deprecated` fails directly;
11+
* - `@ts-expect-error` pins invalid combinations at the compiler level.
12+
*/
13+
import {
14+
useViewModelInstance,
15+
type UseViewModelInstanceFileParams,
16+
type UseViewModelInstanceRequiredResult,
17+
type UseViewModelInstanceResult,
18+
} from '../useViewModelInstance';
19+
import type { RiveFile } from '../../specs/RiveFile.nitro';
20+
import type { ViewModel } from '../../specs/ViewModel.nitro';
21+
import type { RiveViewRef } from '../../index';
22+
23+
export function useDeprecationResolutionPins(
24+
file: RiveFile,
25+
vm: ViewModel,
26+
ref: RiveViewRef
27+
) {
28+
// ── must be DEPRECATED (directive required; unused directive = lint error)
29+
// eslint-disable-next-line @typescript-eslint/no-deprecated
30+
const bare = useViewModelInstance(file);
31+
// eslint-disable-next-line @typescript-eslint/no-deprecated
32+
const noAsync = useViewModelInstance(file, { instanceName: 'X' });
33+
// eslint-disable-next-line @typescript-eslint/no-deprecated
34+
const asyncFalse = useViewModelInstance(file, { async: false });
35+
36+
const widened = { async: true }; // widens to { async: boolean }
37+
// eslint-disable-next-line @typescript-eslint/no-deprecated
38+
const fromWidened = useViewModelInstance(file, widened);
39+
40+
const viaExportedType: UseViewModelInstanceFileParams = { async: true };
41+
// eslint-disable-next-line @typescript-eslint/no-deprecated
42+
const fromExportedType = useViewModelInstance(file, viaExportedType);
43+
44+
// eslint-disable-next-line @typescript-eslint/no-deprecated
45+
const vmSource = useViewModelInstance(vm, { name: 'X' });
46+
// eslint-disable-next-line @typescript-eslint/no-deprecated
47+
const refSource = useViewModelInstance(ref);
48+
49+
// ── must stay NON-deprecated (bare calls; no-deprecated fails on regress)
50+
const asyncFile = useViewModelInstance(file, { async: true });
51+
const asyncArtboard = useViewModelInstance(file, {
52+
async: true,
53+
artboardName: 'Main',
54+
});
55+
const asyncVm = useViewModelInstance(vm, { async: true, useNew: true });
56+
const asyncRef = useViewModelInstance(ref, { async: true });
57+
// The documented fix for widened params re-pins the literal at the call:
58+
const repinned = useViewModelInstance(file, {
59+
...viaExportedType,
60+
async: true,
61+
});
62+
63+
// ── required narrowing only with a literal async on a non-null source
64+
const req: UseViewModelInstanceRequiredResult = useViewModelInstance(file, {
65+
async: true,
66+
required: true,
67+
});
68+
// Widened params lose the narrowing (resolve to the plain deprecated
69+
// overload) but must still compile:
70+
// eslint-disable-next-line @typescript-eslint/no-deprecated
71+
const reqWidened: UseViewModelInstanceResult = useViewModelInstance(
72+
file,
73+
viaExportedType
74+
);
75+
76+
// ── invalid combinations must not compile
77+
// @ts-expect-error artboardName and viewModelName are mutually exclusive
78+
const invalid = useViewModelInstance(file, {
79+
async: true,
80+
artboardName: 'A',
81+
viewModelName: 'B',
82+
});
83+
84+
return {
85+
bare,
86+
noAsync,
87+
asyncFalse,
88+
fromWidened,
89+
fromExportedType,
90+
vmSource,
91+
refSource,
92+
asyncFile,
93+
asyncArtboard,
94+
asyncVm,
95+
asyncRef,
96+
repinned,
97+
req,
98+
reqWidened,
99+
invalid,
100+
};
101+
}
102+
103+
// jest picks up every file under __tests__ — the real gates for this file
104+
// are `yarn tsc` and `yarn lint` (see header).
105+
describe('useViewModelInstance deprecation type pins', () => {
106+
it('is enforced by tsc and eslint, not at runtime', () => {
107+
expect(typeof useDeprecationResolutionPins).toBe('function');
108+
});
109+
});

0 commit comments

Comments
 (0)